Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / extjs / ext-all-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 Commercial Usage
10 Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
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 /**
16  * @class Ext
17  * @singleton
18  */
19 (function() {
20     var global = this,
21         objectPrototype = Object.prototype,
22         toString = objectPrototype.toString,
23         enumerables = true,
24         enumerablesTest = { toString: 1 },
25         i;
26
27     if (typeof Ext === 'undefined') {
28         global.Ext = {};
29     }
30
31     Ext.global = global;
32
33     for (i in enumerablesTest) {
34         enumerables = null;
35     }
36
37     if (enumerables) {
38         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
39                        'toLocaleString', 'toString', 'constructor'];
40     }
41
42     /**
43      * An array containing extra enumerables for old browsers
44      * @property {String[]}
45      */
46     Ext.enumerables = enumerables;
47
48     /**
49      * Copies all the properties of config to the specified object.
50      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
51      * {@link Ext.Object#merge} instead.
52      * @param {Object} object The receiver of the properties
53      * @param {Object} config The source of the properties
54      * @param {Object} defaults A different object that will also be applied for default values
55      * @return {Object} returns obj
56      */
57     Ext.apply = function(object, config, defaults) {
58         if (defaults) {
59             Ext.apply(object, defaults);
60         }
61
62         if (object && config && typeof config === 'object') {
63             var i, j, k;
64
65             for (i in config) {
66                 object[i] = config[i];
67             }
68
69             if (enumerables) {
70                 for (j = enumerables.length; j--;) {
71                     k = enumerables[j];
72                     if (config.hasOwnProperty(k)) {
73                         object[k] = config[k];
74                     }
75                 }
76             }
77         }
78
79         return object;
80     };
81
82     Ext.buildSettings = Ext.apply({
83         baseCSSPrefix: 'x-',
84         scopeResetCSS: false
85     }, Ext.buildSettings || {});
86
87     Ext.apply(Ext, {
88         /**
89          * A reusable empty function
90          */
91         emptyFn: function() {},
92
93         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
94
95         /**
96          * Copies all the properties of config to object if they don't already exist.
97          * @param {Object} object The receiver of the properties
98          * @param {Object} config The source of the properties
99          * @return {Object} returns obj
100          */
101         applyIf: function(object, config) {
102             var property;
103
104             if (object) {
105                 for (property in config) {
106                     if (object[property] === undefined) {
107                         object[property] = config[property];
108                     }
109                 }
110             }
111
112             return object;
113         },
114
115         /**
116          * Iterates either an array or an object. This method delegates to
117          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
118          *
119          * @param {Object/Array} object The object or array to be iterated.
120          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
121          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
122          * type that is being iterated.
123          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
124          * Defaults to the object being iterated itself.
125          * @markdown
126          */
127         iterate: function(object, fn, scope) {
128             if (Ext.isEmpty(object)) {
129                 return;
130             }
131
132             if (scope === undefined) {
133                 scope = object;
134             }
135
136             if (Ext.isIterable(object)) {
137                 Ext.Array.each.call(Ext.Array, object, fn, scope);
138             }
139             else {
140                 Ext.Object.each.call(Ext.Object, object, fn, scope);
141             }
142         }
143     });
144
145     Ext.apply(Ext, {
146
147         /**
148          * This method deprecated. Use {@link Ext#define Ext.define} instead.
149          * @method
150          * @param {Function} superclass
151          * @param {Object} overrides
152          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
153          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
154          */
155         extend: function() {
156             // inline overrides
157             var objectConstructor = objectPrototype.constructor,
158                 inlineOverrides = function(o) {
159                 for (var m in o) {
160                     if (!o.hasOwnProperty(m)) {
161                         continue;
162                     }
163                     this[m] = o[m];
164                 }
165             };
166
167             return function(subclass, superclass, overrides) {
168                 // First we check if the user passed in just the superClass with overrides
169                 if (Ext.isObject(superclass)) {
170                     overrides = superclass;
171                     superclass = subclass;
172                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
173                         superclass.apply(this, arguments);
174                     };
175                 }
176
177
178                 // We create a new temporary class
179                 var F = function() {},
180                     subclassProto, superclassProto = superclass.prototype;
181
182                 F.prototype = superclassProto;
183                 subclassProto = subclass.prototype = new F();
184                 subclassProto.constructor = subclass;
185                 subclass.superclass = superclassProto;
186
187                 if (superclassProto.constructor === objectConstructor) {
188                     superclassProto.constructor = superclass;
189                 }
190
191                 subclass.override = function(overrides) {
192                     Ext.override(subclass, overrides);
193                 };
194
195                 subclassProto.override = inlineOverrides;
196                 subclassProto.proto = subclassProto;
197
198                 subclass.override(overrides);
199                 subclass.extend = function(o) {
200                     return Ext.extend(subclass, o);
201                 };
202
203                 return subclass;
204             };
205         }(),
206
207         /**
208          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
209
210     Ext.define('My.cool.Class', {
211         sayHi: function() {
212             alert('Hi!');
213         }
214     }
215
216     Ext.override(My.cool.Class, {
217         sayHi: function() {
218             alert('About to say...');
219
220             this.callOverridden();
221         }
222     });
223
224     var cool = new My.cool.Class();
225     cool.sayHi(); // alerts 'About to say...'
226                   // alerts 'Hi!'
227
228          * Please note that `this.callOverridden()` only works if the class was previously
229          * created with {@link Ext#define)
230          *
231          * @param {Object} cls The class to override
232          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
233          * containing one or more methods.
234          * @method override
235          * @markdown
236          */
237         override: function(cls, overrides) {
238             if (cls.prototype.$className) {
239                 return cls.override(overrides);
240             }
241             else {
242                 Ext.apply(cls.prototype, overrides);
243             }
244         }
245     });
246
247     // A full set of static methods to do type checking
248     Ext.apply(Ext, {
249
250         /**
251          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
252          * value (second argument) otherwise.
253          *
254          * @param {Object} value The value to test
255          * @param {Object} defaultValue The value to return if the original value is empty
256          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
257          * @return {Object} value, if non-empty, else defaultValue
258          */
259         valueFrom: function(value, defaultValue, allowBlank){
260             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
261         },
262
263         /**
264          * Returns the type of the given variable in string format. List of possible values are:
265          *
266          * - `undefined`: If the given value is `undefined`
267          * - `null`: If the given value is `null`
268          * - `string`: If the given value is a string
269          * - `number`: If the given value is a number
270          * - `boolean`: If the given value is a boolean value
271          * - `date`: If the given value is a `Date` object
272          * - `function`: If the given value is a function reference
273          * - `object`: If the given value is an object
274          * - `array`: If the given value is an array
275          * - `regexp`: If the given value is a regular expression
276          * - `element`: If the given value is a DOM Element
277          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
278          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
279          *
280          * @param {Object} value
281          * @return {String}
282          * @markdown
283          */
284         typeOf: function(value) {
285             if (value === null) {
286                 return 'null';
287             }
288
289             var type = typeof value;
290
291             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
292                 return type;
293             }
294
295             var typeToString = toString.call(value);
296
297             switch(typeToString) {
298                 case '[object Array]':
299                     return 'array';
300                 case '[object Date]':
301                     return 'date';
302                 case '[object Boolean]':
303                     return 'boolean';
304                 case '[object Number]':
305                     return 'number';
306                 case '[object RegExp]':
307                     return 'regexp';
308             }
309
310             if (type === 'function') {
311                 return 'function';
312             }
313
314             if (type === 'object') {
315                 if (value.nodeType !== undefined) {
316                     if (value.nodeType === 3) {
317                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
318                     }
319                     else {
320                         return 'element';
321                     }
322                 }
323
324                 return 'object';
325             }
326
327         },
328
329         /**
330          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
331          *
332          * - `null`
333          * - `undefined`
334          * - a zero-length array
335          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
336          *
337          * @param {Object} value The value to test
338          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
339          * @return {Boolean}
340          * @markdown
341          */
342         isEmpty: function(value, allowEmptyString) {
343             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
344         },
345
346         /**
347          * Returns true if the passed value is a JavaScript Array, false otherwise.
348          *
349          * @param {Object} target The target to test
350          * @return {Boolean}
351          * @method
352          */
353         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
354             return toString.call(value) === '[object Array]';
355         },
356
357         /**
358          * Returns true if the passed value is a JavaScript Date object, false otherwise.
359          * @param {Object} object The object to test
360          * @return {Boolean}
361          */
362         isDate: function(value) {
363             return toString.call(value) === '[object Date]';
364         },
365
366         /**
367          * Returns true if the passed value is a JavaScript Object, false otherwise.
368          * @param {Object} value The value to test
369          * @return {Boolean}
370          * @method
371          */
372         isObject: (toString.call(null) === '[object Object]') ?
373         function(value) {
374             // check ownerDocument here as well to exclude DOM nodes
375             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
376         } :
377         function(value) {
378             return toString.call(value) === '[object Object]';
379         },
380
381         /**
382          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
383          * @param {Object} value The value to test
384          * @return {Boolean}
385          */
386         isPrimitive: function(value) {
387             var type = typeof value;
388
389             return type === 'string' || type === 'number' || type === 'boolean';
390         },
391
392         /**
393          * Returns true if the passed value is a JavaScript Function, false otherwise.
394          * @param {Object} value The value to test
395          * @return {Boolean}
396          * @method
397          */
398         isFunction:
399         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
400         // Object.prorotype.toString (slower)
401         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
402             return toString.call(value) === '[object Function]';
403         } : function(value) {
404             return typeof value === 'function';
405         },
406
407         /**
408          * Returns true if the passed value is a number. Returns false for non-finite numbers.
409          * @param {Object} value The value to test
410          * @return {Boolean}
411          */
412         isNumber: function(value) {
413             return typeof value === 'number' && isFinite(value);
414         },
415
416         /**
417          * Validates that a value is numeric.
418          * @param {Object} value Examples: 1, '1', '2.34'
419          * @return {Boolean} True if numeric, false otherwise
420          */
421         isNumeric: function(value) {
422             return !isNaN(parseFloat(value)) && isFinite(value);
423         },
424
425         /**
426          * Returns true if the passed value is a string.
427          * @param {Object} value The value to test
428          * @return {Boolean}
429          */
430         isString: function(value) {
431             return typeof value === 'string';
432         },
433
434         /**
435          * Returns true if the passed value is a boolean.
436          *
437          * @param {Object} value The value to test
438          * @return {Boolean}
439          */
440         isBoolean: function(value) {
441             return typeof value === 'boolean';
442         },
443
444         /**
445          * Returns true if the passed value is an HTMLElement
446          * @param {Object} value The value to test
447          * @return {Boolean}
448          */
449         isElement: function(value) {
450             return value ? value.nodeType === 1 : false;
451         },
452
453         /**
454          * Returns true if the passed value is a TextNode
455          * @param {Object} value The value to test
456          * @return {Boolean}
457          */
458         isTextNode: function(value) {
459             return value ? value.nodeName === "#text" : false;
460         },
461
462         /**
463          * Returns true if the passed value is defined.
464          * @param {Object} value The value to test
465          * @return {Boolean}
466          */
467         isDefined: function(value) {
468             return typeof value !== 'undefined';
469         },
470
471         /**
472          * Returns true if the passed value is iterable, false otherwise
473          * @param {Object} value The value to test
474          * @return {Boolean}
475          */
476         isIterable: function(value) {
477             return (value && typeof value !== 'string') ? value.length !== undefined : false;
478         }
479     });
480
481     Ext.apply(Ext, {
482
483         /**
484          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
485          * @param {Object} item The variable to clone
486          * @return {Object} clone
487          */
488         clone: function(item) {
489             if (item === null || item === undefined) {
490                 return item;
491             }
492
493             // DOM nodes
494             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
495             // recursively
496             if (item.nodeType && item.cloneNode) {
497                 return item.cloneNode(true);
498             }
499
500             var type = toString.call(item);
501
502             // Date
503             if (type === '[object Date]') {
504                 return new Date(item.getTime());
505             }
506
507             var i, j, k, clone, key;
508
509             // Array
510             if (type === '[object Array]') {
511                 i = item.length;
512
513                 clone = [];
514
515                 while (i--) {
516                     clone[i] = Ext.clone(item[i]);
517                 }
518             }
519             // Object
520             else if (type === '[object Object]' && item.constructor === Object) {
521                 clone = {};
522
523                 for (key in item) {
524                     clone[key] = Ext.clone(item[key]);
525                 }
526
527                 if (enumerables) {
528                     for (j = enumerables.length; j--;) {
529                         k = enumerables[j];
530                         clone[k] = item[k];
531                     }
532                 }
533             }
534
535             return clone || item;
536         },
537
538         /**
539          * @private
540          * Generate a unique reference of Ext in the global scope, useful for sandboxing
541          */
542         getUniqueGlobalNamespace: function() {
543             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
544
545             if (uniqueGlobalNamespace === undefined) {
546                 var i = 0;
547
548                 do {
549                     uniqueGlobalNamespace = 'ExtBox' + (++i);
550                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
551
552                 Ext.global[uniqueGlobalNamespace] = Ext;
553                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
554             }
555
556             return uniqueGlobalNamespace;
557         },
558
559         /**
560          * @private
561          */
562         functionFactory: function() {
563             var args = Array.prototype.slice.call(arguments);
564
565             if (args.length > 0) {
566                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
567                     args[args.length - 1];
568             }
569
570             return Function.prototype.constructor.apply(Function.prototype, args);
571         }
572     });
573
574     /**
575      * Old alias to {@link Ext#typeOf}
576      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
577      * @method
578      * @alias Ext#typeOf
579      */
580     Ext.type = Ext.typeOf;
581
582 })();
583
584 /**
585  * @author Jacky Nguyen <jacky@sencha.com>
586  * @docauthor Jacky Nguyen <jacky@sencha.com>
587  * @class Ext.Version
588  *
589  * A utility class that wrap around a string version number and provide convenient
590  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
591
592     var version = new Ext.Version('1.0.2beta');
593     console.log("Version is " + version); // Version is 1.0.2beta
594
595     console.log(version.getMajor()); // 1
596     console.log(version.getMinor()); // 0
597     console.log(version.getPatch()); // 2
598     console.log(version.getBuild()); // 0
599     console.log(version.getRelease()); // beta
600
601     console.log(version.isGreaterThan('1.0.1')); // True
602     console.log(version.isGreaterThan('1.0.2alpha')); // True
603     console.log(version.isGreaterThan('1.0.2RC')); // False
604     console.log(version.isGreaterThan('1.0.2')); // False
605     console.log(version.isLessThan('1.0.2')); // True
606
607     console.log(version.match(1.0)); // True
608     console.log(version.match('1.0.2')); // True
609
610  * @markdown
611  */
612 (function() {
613
614 // Current core version
615 var version = '4.0.7', Version;
616     Ext.Version = Version = Ext.extend(Object, {
617
618         /**
619          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
620          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
621          * @return {Ext.Version} this
622          */
623         constructor: function(version) {
624             var parts, releaseStartIndex;
625
626             if (version instanceof Version) {
627                 return version;
628             }
629
630             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
631
632             releaseStartIndex = this.version.search(/([^\d\.])/);
633
634             if (releaseStartIndex !== -1) {
635                 this.release = this.version.substr(releaseStartIndex, version.length);
636                 this.shortVersion = this.version.substr(0, releaseStartIndex);
637             }
638
639             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
640
641             parts = this.version.split('.');
642
643             this.major = parseInt(parts.shift() || 0, 10);
644             this.minor = parseInt(parts.shift() || 0, 10);
645             this.patch = parseInt(parts.shift() || 0, 10);
646             this.build = parseInt(parts.shift() || 0, 10);
647
648             return this;
649         },
650
651         /**
652          * Override the native toString method
653          * @private
654          * @return {String} version
655          */
656         toString: function() {
657             return this.version;
658         },
659
660         /**
661          * Override the native valueOf method
662          * @private
663          * @return {String} version
664          */
665         valueOf: function() {
666             return this.version;
667         },
668
669         /**
670          * Returns the major component value
671          * @return {Number} major
672          */
673         getMajor: function() {
674             return this.major || 0;
675         },
676
677         /**
678          * Returns the minor component value
679          * @return {Number} minor
680          */
681         getMinor: function() {
682             return this.minor || 0;
683         },
684
685         /**
686          * Returns the patch component value
687          * @return {Number} patch
688          */
689         getPatch: function() {
690             return this.patch || 0;
691         },
692
693         /**
694          * Returns the build component value
695          * @return {Number} build
696          */
697         getBuild: function() {
698             return this.build || 0;
699         },
700
701         /**
702          * Returns the release component value
703          * @return {Number} release
704          */
705         getRelease: function() {
706             return this.release || '';
707         },
708
709         /**
710          * Returns whether this version if greater than the supplied argument
711          * @param {String/Number} target The version to compare with
712          * @return {Boolean} True if this version if greater than the target, false otherwise
713          */
714         isGreaterThan: function(target) {
715             return Version.compare(this.version, target) === 1;
716         },
717
718         /**
719          * Returns whether this version if smaller than the supplied argument
720          * @param {String/Number} target The version to compare with
721          * @return {Boolean} True if this version if smaller than the target, false otherwise
722          */
723         isLessThan: function(target) {
724             return Version.compare(this.version, target) === -1;
725         },
726
727         /**
728          * Returns whether this version equals to the supplied argument
729          * @param {String/Number} target The version to compare with
730          * @return {Boolean} True if this version equals to the target, false otherwise
731          */
732         equals: function(target) {
733             return Version.compare(this.version, target) === 0;
734         },
735
736         /**
737          * Returns whether this version matches the supplied argument. Example:
738          * <pre><code>
739          * var version = new Ext.Version('1.0.2beta');
740          * console.log(version.match(1)); // True
741          * console.log(version.match(1.0)); // True
742          * console.log(version.match('1.0.2')); // True
743          * console.log(version.match('1.0.2RC')); // False
744          * </code></pre>
745          * @param {String/Number} target The version to compare with
746          * @return {Boolean} True if this version matches the target, false otherwise
747          */
748         match: function(target) {
749             target = String(target);
750             return this.version.substr(0, target.length) === target;
751         },
752
753         /**
754          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
755          * @return {Number[]}
756          */
757         toArray: function() {
758             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
759         },
760
761         /**
762          * Returns shortVersion version without dots and release
763          * @return {String}
764          */
765         getShortVersion: function() {
766             return this.shortVersion;
767         }
768     });
769
770     Ext.apply(Version, {
771         // @private
772         releaseValueMap: {
773             'dev': -6,
774             'alpha': -5,
775             'a': -5,
776             'beta': -4,
777             'b': -4,
778             'rc': -3,
779             '#': -2,
780             'p': -1,
781             'pl': -1
782         },
783
784         /**
785          * Converts a version component to a comparable value
786          *
787          * @static
788          * @param {Object} value The value to convert
789          * @return {Object}
790          */
791         getComponentValue: function(value) {
792             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
793         },
794
795         /**
796          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
797          * they are handled in the following order:
798          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
799          *
800          * @static
801          * @param {String} current The current version to compare to
802          * @param {String} target The target version to compare to
803          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
804          */
805         compare: function(current, target) {
806             var currentValue, targetValue, i;
807
808             current = new Version(current).toArray();
809             target = new Version(target).toArray();
810
811             for (i = 0; i < Math.max(current.length, target.length); i++) {
812                 currentValue = this.getComponentValue(current[i]);
813                 targetValue = this.getComponentValue(target[i]);
814
815                 if (currentValue < targetValue) {
816                     return -1;
817                 } else if (currentValue > targetValue) {
818                     return 1;
819                 }
820             }
821
822             return 0;
823         }
824     });
825
826     Ext.apply(Ext, {
827         /**
828          * @private
829          */
830         versions: {},
831
832         /**
833          * @private
834          */
835         lastRegisteredVersion: null,
836
837         /**
838          * Set version number for the given package name.
839          *
840          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
841          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
842          * @return {Ext}
843          */
844         setVersion: function(packageName, version) {
845             Ext.versions[packageName] = new Version(version);
846             Ext.lastRegisteredVersion = Ext.versions[packageName];
847
848             return this;
849         },
850
851         /**
852          * Get the version number of the supplied package name; will return the last registered version
853          * (last Ext.setVersion call) if there's no package name given.
854          *
855          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
856          * @return {Ext.Version} The version
857          */
858         getVersion: function(packageName) {
859             if (packageName === undefined) {
860                 return Ext.lastRegisteredVersion;
861             }
862
863             return Ext.versions[packageName];
864         },
865
866         /**
867          * Create a closure for deprecated code.
868          *
869     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
870     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
871     // the closure will not be invoked
872     Ext.deprecate('extjs', '4.0.0beta', function() {
873         Ext.oldMethod = Ext.newMethod;
874
875         ...
876     });
877
878          * @param {String} packageName The package name
879          * @param {String} since The last version before it's deprecated
880          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
881          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
882          * @markdown
883          */
884         deprecate: function(packageName, since, closure, scope) {
885             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
886                 closure.call(scope);
887             }
888         }
889     }); // End Versioning
890
891     Ext.setVersion('core', version);
892
893 })();
894
895 /**
896  * @class Ext.String
897  *
898  * A collection of useful static methods to deal with strings
899  * @singleton
900  */
901
902 Ext.String = {
903     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,
904     escapeRe: /('|\\)/g,
905     formatRe: /\{(\d+)\}/g,
906     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
907
908     /**
909      * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
910      * @param {String} value The string to encode
911      * @return {String} The encoded text
912      * @method
913      */
914     htmlEncode: (function() {
915         var entities = {
916             '&': '&amp;',
917             '>': '&gt;',
918             '<': '&lt;',
919             '"': '&quot;'
920         }, keys = [], p, regex;
921         
922         for (p in entities) {
923             keys.push(p);
924         }
925         
926         regex = new RegExp('(' + keys.join('|') + ')', 'g');
927         
928         return function(value) {
929             return (!value) ? value : String(value).replace(regex, function(match, capture) {
930                 return entities[capture];    
931             });
932         };
933     })(),
934
935     /**
936      * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
937      * @param {String} value The string to decode
938      * @return {String} The decoded text
939      * @method
940      */
941     htmlDecode: (function() {
942         var entities = {
943             '&amp;': '&',
944             '&gt;': '>',
945             '&lt;': '<',
946             '&quot;': '"'
947         }, keys = [], p, regex;
948         
949         for (p in entities) {
950             keys.push(p);
951         }
952         
953         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
954         
955         return function(value) {
956             return (!value) ? value : String(value).replace(regex, function(match, capture) {
957                 if (capture in entities) {
958                     return entities[capture];
959                 } else {
960                     return String.fromCharCode(parseInt(capture.substr(2), 10));
961                 }
962             });
963         };
964     })(),
965
966     /**
967      * Appends content to the query string of a URL, handling logic for whether to place
968      * a question mark or ampersand.
969      * @param {String} url The URL to append to.
970      * @param {String} string The content to append to the URL.
971      * @return (String) The resulting URL
972      */
973     urlAppend : function(url, string) {
974         if (!Ext.isEmpty(string)) {
975             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
976         }
977
978         return url;
979     },
980
981     /**
982      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
983      * @example
984 var s = '  foo bar  ';
985 alert('-' + s + '-');         //alerts "- foo bar -"
986 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
987
988      * @param {String} string The string to escape
989      * @return {String} The trimmed string
990      */
991     trim: function(string) {
992         return string.replace(Ext.String.trimRegex, "");
993     },
994
995     /**
996      * Capitalize the given string
997      * @param {String} string
998      * @return {String}
999      */
1000     capitalize: function(string) {
1001         return string.charAt(0).toUpperCase() + string.substr(1);
1002     },
1003
1004     /**
1005      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1006      * @param {String} value The string to truncate
1007      * @param {Number} length The maximum length to allow before truncating
1008      * @param {Boolean} word True to try to find a common word break
1009      * @return {String} The converted text
1010      */
1011     ellipsis: function(value, len, word) {
1012         if (value && value.length > len) {
1013             if (word) {
1014                 var vs = value.substr(0, len - 2),
1015                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1016                 if (index !== -1 && index >= (len - 15)) {
1017                     return vs.substr(0, index) + "...";
1018                 }
1019             }
1020             return value.substr(0, len - 3) + "...";
1021         }
1022         return value;
1023     },
1024
1025     /**
1026      * Escapes the passed string for use in a regular expression
1027      * @param {String} string
1028      * @return {String}
1029      */
1030     escapeRegex: function(string) {
1031         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1032     },
1033
1034     /**
1035      * Escapes the passed string for ' and \
1036      * @param {String} string The string to escape
1037      * @return {String} The escaped string
1038      */
1039     escape: function(string) {
1040         return string.replace(Ext.String.escapeRe, "\\$1");
1041     },
1042
1043     /**
1044      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1045      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1046      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1047      * but does not change the current string.
1048      * <pre><code>
1049     // alternate sort directions
1050     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1051
1052     // instead of conditional logic:
1053     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1054        </code></pre>
1055      * @param {String} string The current string
1056      * @param {String} value The value to compare to the current string
1057      * @param {String} other The new value to use if the string already equals the first value passed in
1058      * @return {String} The new value
1059      */
1060     toggle: function(string, value, other) {
1061         return string === value ? other : value;
1062     },
1063
1064     /**
1065      * Pads the left side of a string with a specified character.  This is especially useful
1066      * for normalizing number and date strings.  Example usage:
1067      *
1068      * <pre><code>
1069 var s = Ext.String.leftPad('123', 5, '0');
1070 // s now contains the string: '00123'
1071        </code></pre>
1072      * @param {String} string The original string
1073      * @param {Number} size The total length of the output string
1074      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1075      * @return {String} The padded string
1076      */
1077     leftPad: function(string, size, character) {
1078         var result = String(string);
1079         character = character || " ";
1080         while (result.length < size) {
1081             result = character + result;
1082         }
1083         return result;
1084     },
1085
1086     /**
1087      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1088      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1089      * <pre><code>
1090 var cls = 'my-class', text = 'Some text';
1091 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1092 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1093        </code></pre>
1094      * @param {String} string The tokenized string to be formatted
1095      * @param {String} value1 The value to replace token {0}
1096      * @param {String} value2 Etc...
1097      * @return {String} The formatted string
1098      */
1099     format: function(format) {
1100         var args = Ext.Array.toArray(arguments, 1);
1101         return format.replace(Ext.String.formatRe, function(m, i) {
1102             return args[i];
1103         });
1104     },
1105
1106     /**
1107      * Returns a string with a specified number of repititions a given string pattern.
1108      * The pattern be separated by a different string.
1109      *
1110      *      var s = Ext.String.repeat('---', 4); // = '------------'
1111      *      var t = Ext.String.repeat('--', 3, '/'); // = '--/--/--'
1112      *
1113      * @param {String} pattern The pattern to repeat.
1114      * @param {Number} count The number of times to repeat the pattern (may be 0).
1115      * @param {String} sep An option string to separate each pattern.
1116      */
1117     repeat: function(pattern, count, sep) {
1118         for (var buf = [], i = count; i--; ) {
1119             buf.push(pattern);
1120         }
1121         return buf.join(sep || '');
1122     }
1123 };
1124
1125 /**
1126  * @class Ext.Number
1127  *
1128  * A collection of useful static methods to deal with numbers
1129  * @singleton
1130  */
1131
1132 (function() {
1133
1134 var isToFixedBroken = (0.9).toFixed() !== '1';
1135
1136 Ext.Number = {
1137     /**
1138      * Checks whether or not the passed number is within a desired range.  If the number is already within the
1139      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1140      * exceeded. Note that this method returns the constrained value but does not change the current number.
1141      * @param {Number} number The number to check
1142      * @param {Number} min The minimum number in the range
1143      * @param {Number} max The maximum number in the range
1144      * @return {Number} The constrained value if outside the range, otherwise the current value
1145      */
1146     constrain: function(number, min, max) {
1147         number = parseFloat(number);
1148
1149         if (!isNaN(min)) {
1150             number = Math.max(number, min);
1151         }
1152         if (!isNaN(max)) {
1153             number = Math.min(number, max);
1154         }
1155         return number;
1156     },
1157
1158     /**
1159      * Snaps the passed number between stopping points based upon a passed increment value.
1160      * @param {Number} value The unsnapped value.
1161      * @param {Number} increment The increment by which the value must move.
1162      * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1163      * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1164      * @return {Number} The value of the nearest snap target.
1165      */
1166     snap : function(value, increment, minValue, maxValue) {
1167         var newValue = value,
1168             m;
1169
1170         if (!(increment && value)) {
1171             return value;
1172         }
1173         m = value % increment;
1174         if (m !== 0) {
1175             newValue -= m;
1176             if (m * 2 >= increment) {
1177                 newValue += increment;
1178             } else if (m * 2 < -increment) {
1179                 newValue -= increment;
1180             }
1181         }
1182         return Ext.Number.constrain(newValue, minValue,  maxValue);
1183     },
1184
1185     /**
1186      * Formats a number using fixed-point notation
1187      * @param {Number} value The number to format
1188      * @param {Number} precision The number of digits to show after the decimal point
1189      */
1190     toFixed: function(value, precision) {
1191         if (isToFixedBroken) {
1192             precision = precision || 0;
1193             var pow = Math.pow(10, precision);
1194             return (Math.round(value * pow) / pow).toFixed(precision);
1195         }
1196
1197         return value.toFixed(precision);
1198     },
1199
1200     /**
1201      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1202      * it is not.
1203
1204 Ext.Number.from('1.23', 1); // returns 1.23
1205 Ext.Number.from('abc', 1); // returns 1
1206
1207      * @param {Object} value
1208      * @param {Number} defaultValue The value to return if the original value is non-numeric
1209      * @return {Number} value, if numeric, defaultValue otherwise
1210      */
1211     from: function(value, defaultValue) {
1212         if (isFinite(value)) {
1213             value = parseFloat(value);
1214         }
1215
1216         return !isNaN(value) ? value : defaultValue;
1217     }
1218 };
1219
1220 })();
1221
1222 /**
1223  * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
1224  * @member Ext
1225  * @method num
1226  * @alias Ext.Number#from
1227  */
1228 Ext.num = function() {
1229     return Ext.Number.from.apply(this, arguments);
1230 };
1231 /**
1232  * @class Ext.Array
1233  * @singleton
1234  * @author Jacky Nguyen <jacky@sencha.com>
1235  * @docauthor Jacky Nguyen <jacky@sencha.com>
1236  *
1237  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1238  */
1239 (function() {
1240
1241     var arrayPrototype = Array.prototype,
1242         slice = arrayPrototype.slice,
1243         supportsSplice = function () {
1244             var array = [],
1245                 lengthBefore,
1246                 j = 20;
1247
1248             if (!array.splice) {
1249                 return false;
1250             }
1251
1252             // This detects a bug in IE8 splice method:
1253             // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1254
1255             while (j--) {
1256                 array.push("A");
1257             }
1258
1259             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");
1260
1261             lengthBefore = array.length; //41
1262             array.splice(13, 0, "XXX"); // add one element
1263
1264             if (lengthBefore+1 != array.length) {
1265                 return false;
1266             }
1267             // end IE8 bug
1268
1269             return true;
1270         }(),
1271         supportsForEach = 'forEach' in arrayPrototype,
1272         supportsMap = 'map' in arrayPrototype,
1273         supportsIndexOf = 'indexOf' in arrayPrototype,
1274         supportsEvery = 'every' in arrayPrototype,
1275         supportsSome = 'some' in arrayPrototype,
1276         supportsFilter = 'filter' in arrayPrototype,
1277         supportsSort = function() {
1278             var a = [1,2,3,4,5].sort(function(){ return 0; });
1279             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1280         }(),
1281         supportsSliceOnNodeList = true,
1282         ExtArray;
1283
1284     try {
1285         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1286         if (typeof document !== 'undefined') {
1287             slice.call(document.getElementsByTagName('body'));
1288         }
1289     } catch (e) {
1290         supportsSliceOnNodeList = false;
1291     }
1292
1293     function fixArrayIndex (array, index) {
1294         return (index < 0) ? Math.max(0, array.length + index)
1295                            : Math.min(array.length, index);
1296     }
1297
1298     /*
1299     Does the same work as splice, but with a slightly more convenient signature. The splice
1300     method has bugs in IE8, so this is the implementation we use on that platform.
1301
1302     The rippling of items in the array can be tricky. Consider two use cases:
1303
1304                   index=2
1305                   removeCount=2
1306                  /=====\
1307         +---+---+---+---+---+---+---+---+
1308         | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1309         +---+---+---+---+---+---+---+---+
1310                          /  \/  \/  \/  \
1311                         /   /\  /\  /\   \
1312                        /   /  \/  \/  \   +--------------------------+
1313                       /   /   /\  /\   +--------------------------+   \
1314                      /   /   /  \/  +--------------------------+   \   \
1315                     /   /   /   /+--------------------------+   \   \   \
1316                    /   /   /   /                             \   \   \   \
1317                   v   v   v   v                               v   v   v   v
1318         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1319         | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1320         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
1321         A                               B        \=========/
1322                                                  insert=[a,b,c]
1323
1324     In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1325     that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1326     must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1327     */
1328     function replaceSim (array, index, removeCount, insert) {
1329         var add = insert ? insert.length : 0,
1330             length = array.length,
1331             pos = fixArrayIndex(array, index);
1332
1333         // we try to use Array.push when we can for efficiency...
1334         if (pos === length) {
1335             if (add) {
1336                 array.push.apply(array, insert);
1337             }
1338         } else {
1339             var remove = Math.min(removeCount, length - pos),
1340                 tailOldPos = pos + remove,
1341                 tailNewPos = tailOldPos + add - remove,
1342                 tailCount = length - tailOldPos,
1343                 lengthAfterRemove = length - remove,
1344                 i;
1345
1346             if (tailNewPos < tailOldPos) { // case A
1347                 for (i = 0; i < tailCount; ++i) {
1348                     array[tailNewPos+i] = array[tailOldPos+i];
1349                 }
1350             } else if (tailNewPos > tailOldPos) { // case B
1351                 for (i = tailCount; i--; ) {
1352                     array[tailNewPos+i] = array[tailOldPos+i];
1353                 }
1354             } // else, add == remove (nothing to do)
1355
1356             if (add && pos === lengthAfterRemove) {
1357                 array.length = lengthAfterRemove; // truncate array
1358                 array.push.apply(array, insert);
1359             } else {
1360                 array.length = lengthAfterRemove + add; // reserves space
1361                 for (i = 0; i < add; ++i) {
1362                     array[pos+i] = insert[i];
1363                 }
1364             }
1365         }
1366
1367         return array;
1368     }
1369
1370     function replaceNative (array, index, removeCount, insert) {
1371         if (insert && insert.length) {
1372             if (index < array.length) {
1373                 array.splice.apply(array, [index, removeCount].concat(insert));
1374             } else {
1375                 array.push.apply(array, insert);
1376             }
1377         } else {
1378             array.splice(index, removeCount);
1379         }
1380         return array;
1381     }
1382
1383     function eraseSim (array, index, removeCount) {
1384         return replaceSim(array, index, removeCount);
1385     }
1386
1387     function eraseNative (array, index, removeCount) {
1388         array.splice(index, removeCount);
1389         return array;
1390     }
1391
1392     function spliceSim (array, index, removeCount) {
1393         var pos = fixArrayIndex(array, index),
1394             removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1395
1396         if (arguments.length < 4) {
1397             replaceSim(array, pos, removeCount);
1398         } else {
1399             replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1400         }
1401
1402         return removed;
1403     }
1404
1405     function spliceNative (array) {
1406         return array.splice.apply(array, slice.call(arguments, 1));
1407     }
1408
1409     var erase = supportsSplice ? eraseNative : eraseSim,
1410         replace = supportsSplice ? replaceNative : replaceSim,
1411         splice = supportsSplice ? spliceNative : spliceSim;
1412
1413     // NOTE: from here on, use erase, replace or splice (not native methods)...
1414
1415     ExtArray = Ext.Array = {
1416         /**
1417          * Iterates an array or an iterable value and invoke the given callback function for each item.
1418          *
1419          *     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1420          *
1421          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1422          *         console.log(name);
1423          *     });
1424          *
1425          *     var sum = function() {
1426          *         var sum = 0;
1427          *
1428          *         Ext.Array.each(arguments, function(value) {
1429          *             sum += value;
1430          *         });
1431          *
1432          *         return sum;
1433          *     };
1434          *
1435          *     sum(1, 2, 3); // returns 6
1436          *
1437          * The iteration can be stopped by returning false in the function callback.
1438          *
1439          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1440          *         if (name === 'Singapore') {
1441          *             return false; // break here
1442          *         }
1443          *     });
1444          *
1445          * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1446          *
1447          * @param {Array/NodeList/Object} iterable The value to be iterated. If this
1448          * argument is not iterable, the callback function is called once.
1449          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1450          * the current `index`.
1451          * @param {Object} fn.item The item at the current `index` in the passed `array`
1452          * @param {Number} fn.index The current `index` within the `array`
1453          * @param {Array} fn.allItems The `array` itself which was passed as the first argument
1454          * @param {Boolean} fn.return Return false to stop iteration.
1455          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1456          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1457          * Defaults false
1458          * @return {Boolean} See description for the `fn` parameter.
1459          */
1460         each: function(array, fn, scope, reverse) {
1461             array = ExtArray.from(array);
1462
1463             var i,
1464                 ln = array.length;
1465
1466             if (reverse !== true) {
1467                 for (i = 0; i < ln; i++) {
1468                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1469                         return i;
1470                     }
1471                 }
1472             }
1473             else {
1474                 for (i = ln - 1; i > -1; i--) {
1475                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1476                         return i;
1477                     }
1478                 }
1479             }
1480
1481             return true;
1482         },
1483
1484         /**
1485          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1486          * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
1487          * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
1488          * could be much better in modern browsers comparing with {@link Ext.Array#each}
1489          *
1490          * @param {Array} array The array to iterate
1491          * @param {Function} fn The callback function.
1492          * @param {Object} fn.item The item at the current `index` in the passed `array`
1493          * @param {Number} fn.index The current `index` within the `array`
1494          * @param {Array}  fn.allItems The `array` itself which was passed as the first argument
1495          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1496          */
1497         forEach: function(array, fn, scope) {
1498             if (supportsForEach) {
1499                 return array.forEach(fn, scope);
1500             }
1501
1502             var i = 0,
1503                 ln = array.length;
1504
1505             for (; i < ln; i++) {
1506                 fn.call(scope, array[i], i, array);
1507             }
1508         },
1509
1510         /**
1511          * Get the index of the provided `item` in the given `array`, a supplement for the
1512          * missing arrayPrototype.indexOf in Internet Explorer.
1513          *
1514          * @param {Array} array The array to check
1515          * @param {Object} item The item to look for
1516          * @param {Number} from (Optional) The index at which to begin the search
1517          * @return {Number} The index of item in the array (or -1 if it is not found)
1518          */
1519         indexOf: function(array, item, from) {
1520             if (supportsIndexOf) {
1521                 return array.indexOf(item, from);
1522             }
1523
1524             var i, length = array.length;
1525
1526             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1527                 if (array[i] === item) {
1528                     return i;
1529                 }
1530             }
1531
1532             return -1;
1533         },
1534
1535         /**
1536          * Checks whether or not the given `array` contains the specified `item`
1537          *
1538          * @param {Array} array The array to check
1539          * @param {Object} item The item to look for
1540          * @return {Boolean} True if the array contains the item, false otherwise
1541          */
1542         contains: function(array, item) {
1543             if (supportsIndexOf) {
1544                 return array.indexOf(item) !== -1;
1545             }
1546
1547             var i, ln;
1548
1549             for (i = 0, ln = array.length; i < ln; i++) {
1550                 if (array[i] === item) {
1551                     return true;
1552                 }
1553             }
1554
1555             return false;
1556         },
1557
1558         /**
1559          * Converts any iterable (numeric indices and a length property) into a true array.
1560          *
1561          *     function test() {
1562          *         var args = Ext.Array.toArray(arguments),
1563          *             fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1564          *
1565          *         alert(args.join(' '));
1566          *         alert(fromSecondToLastArgs.join(' '));
1567          *     }
1568          *
1569          *     test('just', 'testing', 'here'); // alerts 'just testing here';
1570          *                                      // alerts 'testing here';
1571          *
1572          *     Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1573          *     Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1574          *     Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1575          *
1576          * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1577          *
1578          * @param {Object} iterable the iterable object to be turned into a true Array.
1579          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1580          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1581          * index of the iterable value
1582          * @return {Array} array
1583          */
1584         toArray: function(iterable, start, end){
1585             if (!iterable || !iterable.length) {
1586                 return [];
1587             }
1588
1589             if (typeof iterable === 'string') {
1590                 iterable = iterable.split('');
1591             }
1592
1593             if (supportsSliceOnNodeList) {
1594                 return slice.call(iterable, start || 0, end || iterable.length);
1595             }
1596
1597             var array = [],
1598                 i;
1599
1600             start = start || 0;
1601             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1602
1603             for (i = start; i < end; i++) {
1604                 array.push(iterable[i]);
1605             }
1606
1607             return array;
1608         },
1609
1610         /**
1611          * Plucks the value of a property from each item in the Array. Example:
1612          *
1613          *     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1614          *
1615          * @param {Array/NodeList} array The Array of items to pluck the value from.
1616          * @param {String} propertyName The property name to pluck from each element.
1617          * @return {Array} The value from each item in the Array.
1618          */
1619         pluck: function(array, propertyName) {
1620             var ret = [],
1621                 i, ln, item;
1622
1623             for (i = 0, ln = array.length; i < ln; i++) {
1624                 item = array[i];
1625
1626                 ret.push(item[propertyName]);
1627             }
1628
1629             return ret;
1630         },
1631
1632         /**
1633          * Creates a new array with the results of calling a provided function on every element in this array.
1634          *
1635          * @param {Array} array
1636          * @param {Function} fn Callback function for each item
1637          * @param {Object} scope Callback function scope
1638          * @return {Array} results
1639          */
1640         map: function(array, fn, scope) {
1641             if (supportsMap) {
1642                 return array.map(fn, scope);
1643             }
1644
1645             var results = [],
1646                 i = 0,
1647                 len = array.length;
1648
1649             for (; i < len; i++) {
1650                 results[i] = fn.call(scope, array[i], i, array);
1651             }
1652
1653             return results;
1654         },
1655
1656         /**
1657          * Executes the specified function for each array element until the function returns a falsy value.
1658          * If such an item is found, the function will return false immediately.
1659          * Otherwise, it will return true.
1660          *
1661          * @param {Array} array
1662          * @param {Function} fn Callback function for each item
1663          * @param {Object} scope Callback function scope
1664          * @return {Boolean} True if no false value is returned by the callback function.
1665          */
1666         every: function(array, fn, scope) {
1667             if (supportsEvery) {
1668                 return array.every(fn, scope);
1669             }
1670
1671             var i = 0,
1672                 ln = array.length;
1673
1674             for (; i < ln; ++i) {
1675                 if (!fn.call(scope, array[i], i, array)) {
1676                     return false;
1677                 }
1678             }
1679
1680             return true;
1681         },
1682
1683         /**
1684          * Executes the specified function for each array element until the function returns a truthy value.
1685          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1686          *
1687          * @param {Array} array
1688          * @param {Function} fn Callback function for each item
1689          * @param {Object} scope Callback function scope
1690          * @return {Boolean} True if the callback function returns a truthy value.
1691          */
1692         some: function(array, fn, scope) {
1693             if (supportsSome) {
1694                 return array.some(fn, scope);
1695             }
1696
1697             var i = 0,
1698                 ln = array.length;
1699
1700             for (; i < ln; ++i) {
1701                 if (fn.call(scope, array[i], i, array)) {
1702                     return true;
1703                 }
1704             }
1705
1706             return false;
1707         },
1708
1709         /**
1710          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1711          *
1712          * See {@link Ext.Array#filter}
1713          *
1714          * @param {Array} array
1715          * @return {Array} results
1716          */
1717         clean: function(array) {
1718             var results = [],
1719                 i = 0,
1720                 ln = array.length,
1721                 item;
1722
1723             for (; i < ln; i++) {
1724                 item = array[i];
1725
1726                 if (!Ext.isEmpty(item)) {
1727                     results.push(item);
1728                 }
1729             }
1730
1731             return results;
1732         },
1733
1734         /**
1735          * Returns a new array with unique items
1736          *
1737          * @param {Array} array
1738          * @return {Array} results
1739          */
1740         unique: function(array) {
1741             var clone = [],
1742                 i = 0,
1743                 ln = array.length,
1744                 item;
1745
1746             for (; i < ln; i++) {
1747                 item = array[i];
1748
1749                 if (ExtArray.indexOf(clone, item) === -1) {
1750                     clone.push(item);
1751                 }
1752             }
1753
1754             return clone;
1755         },
1756
1757         /**
1758          * Creates a new array with all of the elements of this array for which
1759          * the provided filtering function returns true.
1760          *
1761          * @param {Array} array
1762          * @param {Function} fn Callback function for each item
1763          * @param {Object} scope Callback function scope
1764          * @return {Array} results
1765          */
1766         filter: function(array, fn, scope) {
1767             if (supportsFilter) {
1768                 return array.filter(fn, scope);
1769             }
1770
1771             var results = [],
1772                 i = 0,
1773                 ln = array.length;
1774
1775             for (; i < ln; i++) {
1776                 if (fn.call(scope, array[i], i, array)) {
1777                     results.push(array[i]);
1778                 }
1779             }
1780
1781             return results;
1782         },
1783
1784         /**
1785          * Converts a value to an array if it's not already an array; returns:
1786          *
1787          * - An empty array if given value is `undefined` or `null`
1788          * - Itself if given value is already an array
1789          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1790          * - An array with one item which is the given value, otherwise
1791          *
1792          * @param {Object} value The value to convert to an array if it's not already is an array
1793          * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
1794          * defaults to false
1795          * @return {Array} array
1796          */
1797         from: function(value, newReference) {
1798             if (value === undefined || value === null) {
1799                 return [];
1800             }
1801
1802             if (Ext.isArray(value)) {
1803                 return (newReference) ? slice.call(value) : value;
1804             }
1805
1806             if (value && value.length !== undefined && typeof value !== 'string') {
1807                 return Ext.toArray(value);
1808             }
1809
1810             return [value];
1811         },
1812
1813         /**
1814          * Removes the specified item from the array if it exists
1815          *
1816          * @param {Array} array The array
1817          * @param {Object} item The item to remove
1818          * @return {Array} The passed array itself
1819          */
1820         remove: function(array, item) {
1821             var index = ExtArray.indexOf(array, item);
1822
1823             if (index !== -1) {
1824                 erase(array, index, 1);
1825             }
1826
1827             return array;
1828         },
1829
1830         /**
1831          * Push an item into the array only if the array doesn't contain it yet
1832          *
1833          * @param {Array} array The array
1834          * @param {Object} item The item to include
1835          */
1836         include: function(array, item) {
1837             if (!ExtArray.contains(array, item)) {
1838                 array.push(item);
1839             }
1840         },
1841
1842         /**
1843          * Clone a flat array without referencing the previous one. Note that this is different
1844          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1845          * for Array.prototype.slice.call(array)
1846          *
1847          * @param {Array} array The array
1848          * @return {Array} The clone array
1849          */
1850         clone: function(array) {
1851             return slice.call(array);
1852         },
1853
1854         /**
1855          * Merge multiple arrays into one with unique items.
1856          *
1857          * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1858          *
1859          * @param {Array} array1
1860          * @param {Array} array2
1861          * @param {Array} etc
1862          * @return {Array} merged
1863          */
1864         merge: function() {
1865             var args = slice.call(arguments),
1866                 array = [],
1867                 i, ln;
1868
1869             for (i = 0, ln = args.length; i < ln; i++) {
1870                 array = array.concat(args[i]);
1871             }
1872
1873             return ExtArray.unique(array);
1874         },
1875
1876         /**
1877          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1878          *
1879          * @param {Array} array1
1880          * @param {Array} array2
1881          * @param {Array} etc
1882          * @return {Array} intersect
1883          */
1884         intersect: function() {
1885             var intersect = [],
1886                 arrays = slice.call(arguments),
1887                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1888
1889             if (!arrays.length) {
1890                 return intersect;
1891             }
1892
1893             // Find the smallest array
1894             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1895                 if (!minArray || array.length < minArray.length) {
1896                     minArray = array;
1897                     x = i;
1898                 }
1899             }
1900
1901             minArray = ExtArray.unique(minArray);
1902             erase(arrays, x, 1);
1903
1904             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1905             // an item in the small array, we're likely to find it before reaching the end
1906             // of the inner loop and can terminate the search early.
1907             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1908                 var count = 0;
1909
1910                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1911                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1912                         if (x === y) {
1913                             count++;
1914                             break;
1915                         }
1916                     }
1917                 }
1918
1919                 if (count === arraysLn) {
1920                     intersect.push(x);
1921                 }
1922             }
1923
1924             return intersect;
1925         },
1926
1927         /**
1928          * Perform a set difference A-B by subtracting all items in array B from array A.
1929          *
1930          * @param {Array} arrayA
1931          * @param {Array} arrayB
1932          * @return {Array} difference
1933          */
1934         difference: function(arrayA, arrayB) {
1935             var clone = slice.call(arrayA),
1936                 ln = clone.length,
1937                 i, j, lnB;
1938
1939             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1940                 for (j = 0; j < ln; j++) {
1941                     if (clone[j] === arrayB[i]) {
1942                         erase(clone, j, 1);
1943                         j--;
1944                         ln--;
1945                     }
1946                 }
1947             }
1948
1949             return clone;
1950         },
1951
1952         /**
1953          * Returns a shallow copy of a part of an array. This is equivalent to the native
1954          * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1955          * is "arguments" since the arguments object does not supply a slice method but can
1956          * be the context object to Array.prototype.slice.
1957          *
1958          * @param {Array} array The array (or arguments object).
1959          * @param {Number} begin The index at which to begin. Negative values are offsets from
1960          * the end of the array.
1961          * @param {Number} end The index at which to end. The copied items do not include
1962          * end. Negative values are offsets from the end of the array. If end is omitted,
1963          * all items up to the end of the array are copied.
1964          * @return {Array} The copied piece of the array.
1965          */
1966         // Note: IE6 will return [] on slice.call(x, undefined).
1967         slice: ([1,2].slice(1, undefined).length ?
1968             function (array, begin, end) {
1969                 return slice.call(array, begin, end);
1970             } :
1971             // at least IE6 uses arguments.length for variadic signature
1972             function (array, begin, end) {
1973                 // After tested for IE 6, the one below is of the best performance
1974                 // see http://jsperf.com/slice-fix
1975                 if (typeof begin === 'undefined') {
1976                     return slice.call(array);
1977                 }
1978                 if (typeof end === 'undefined') {
1979                     return slice.call(array, begin);
1980                 }
1981                 return slice.call(array, begin, end);
1982             }
1983         ),
1984
1985         /**
1986          * Sorts the elements of an Array.
1987          * By default, this method sorts the elements alphabetically and ascending.
1988          *
1989          * @param {Array} array The array to sort.
1990          * @param {Function} sortFn (optional) The comparison function.
1991          * @return {Array} The sorted array.
1992          */
1993         sort: function(array, sortFn) {
1994             if (supportsSort) {
1995                 if (sortFn) {
1996                     return array.sort(sortFn);
1997                 } else {
1998                     return array.sort();
1999                 }
2000             }
2001
2002             var length = array.length,
2003                 i = 0,
2004                 comparison,
2005                 j, min, tmp;
2006
2007             for (; i < length; i++) {
2008                 min = i;
2009                 for (j = i + 1; j < length; j++) {
2010                     if (sortFn) {
2011                         comparison = sortFn(array[j], array[min]);
2012                         if (comparison < 0) {
2013                             min = j;
2014                         }
2015                     } else if (array[j] < array[min]) {
2016                         min = j;
2017                     }
2018                 }
2019                 if (min !== i) {
2020                     tmp = array[i];
2021                     array[i] = array[min];
2022                     array[min] = tmp;
2023                 }
2024             }
2025
2026             return array;
2027         },
2028
2029         /**
2030          * Recursively flattens into 1-d Array. Injects Arrays inline.
2031          *
2032          * @param {Array} array The array to flatten
2033          * @return {Array} The 1-d array.
2034          */
2035         flatten: function(array) {
2036             var worker = [];
2037
2038             function rFlatten(a) {
2039                 var i, ln, v;
2040
2041                 for (i = 0, ln = a.length; i < ln; i++) {
2042                     v = a[i];
2043
2044                     if (Ext.isArray(v)) {
2045                         rFlatten(v);
2046                     } else {
2047                         worker.push(v);
2048                     }
2049                 }
2050
2051                 return worker;
2052             }
2053
2054             return rFlatten(array);
2055         },
2056
2057         /**
2058          * Returns the minimum value in the Array.
2059          *
2060          * @param {Array/NodeList} array The Array from which to select the minimum value.
2061          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2062          * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2063          * @return {Object} minValue The minimum value
2064          */
2065         min: function(array, comparisonFn) {
2066             var min = array[0],
2067                 i, ln, item;
2068
2069             for (i = 0, ln = array.length; i < ln; i++) {
2070                 item = array[i];
2071
2072                 if (comparisonFn) {
2073                     if (comparisonFn(min, item) === 1) {
2074                         min = item;
2075                     }
2076                 }
2077                 else {
2078                     if (item < min) {
2079                         min = item;
2080                     }
2081                 }
2082             }
2083
2084             return min;
2085         },
2086
2087         /**
2088          * Returns the maximum value in the Array.
2089          *
2090          * @param {Array/NodeList} array The Array from which to select the maximum value.
2091          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2092          * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2093          * @return {Object} maxValue The maximum value
2094          */
2095         max: function(array, comparisonFn) {
2096             var max = array[0],
2097                 i, ln, item;
2098
2099             for (i = 0, ln = array.length; i < ln; i++) {
2100                 item = array[i];
2101
2102                 if (comparisonFn) {
2103                     if (comparisonFn(max, item) === -1) {
2104                         max = item;
2105                     }
2106                 }
2107                 else {
2108                     if (item > max) {
2109                         max = item;
2110                     }
2111                 }
2112             }
2113
2114             return max;
2115         },
2116
2117         /**
2118          * Calculates the mean of all items in the array.
2119          *
2120          * @param {Array} array The Array to calculate the mean value of.
2121          * @return {Number} The mean.
2122          */
2123         mean: function(array) {
2124             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2125         },
2126
2127         /**
2128          * Calculates the sum of all items in the given array.
2129          *
2130          * @param {Array} array The Array to calculate the sum value of.
2131          * @return {Number} The sum.
2132          */
2133         sum: function(array) {
2134             var sum = 0,
2135                 i, ln, item;
2136
2137             for (i = 0,ln = array.length; i < ln; i++) {
2138                 item = array[i];
2139
2140                 sum += item;
2141             }
2142
2143             return sum;
2144         },
2145
2146
2147         /**
2148          * Removes items from an array. This is functionally equivalent to the splice method
2149          * of Array, but works around bugs in IE8's splice method and does not copy the
2150          * removed elements in order to return them (because very often they are ignored).
2151          *
2152          * @param {Array} array The Array on which to replace.
2153          * @param {Number} index The index in the array at which to operate.
2154          * @param {Number} removeCount The number of items to remove at index.
2155          * @return {Array} The array passed.
2156          * @method
2157          */
2158         erase: erase,
2159
2160         /**
2161          * Inserts items in to an array.
2162          *
2163          * @param {Array} array The Array on which to replace.
2164          * @param {Number} index The index in the array at which to operate.
2165          * @param {Array} items The array of items to insert at index.
2166          * @return {Array} The array passed.
2167          */
2168         insert: function (array, index, items) {
2169             return replace(array, index, 0, items);
2170         },
2171
2172         /**
2173          * Replaces items in an array. This is functionally equivalent to the splice method
2174          * of Array, but works around bugs in IE8's splice method and is often more convenient
2175          * to call because it accepts an array of items to insert rather than use a variadic
2176          * argument list.
2177          *
2178          * @param {Array} array The Array on which to replace.
2179          * @param {Number} index The index in the array at which to operate.
2180          * @param {Number} removeCount The number of items to remove at index (can be 0).
2181          * @param {Array} insert (optional) An array of items to insert at index.
2182          * @return {Array} The array passed.
2183          * @method
2184          */
2185         replace: replace,
2186
2187         /**
2188          * Replaces items in an array. This is equivalent to the splice method of Array, but
2189          * works around bugs in IE8's splice method. The signature is exactly the same as the
2190          * splice method except that the array is the first argument. All arguments following
2191          * removeCount are inserted in the array at index.
2192          *
2193          * @param {Array} array The Array on which to replace.
2194          * @param {Number} index The index in the array at which to operate.
2195          * @param {Number} removeCount The number of items to remove at index (can be 0).
2196          * @return {Array} An array containing the removed items.
2197          * @method
2198          */
2199         splice: splice
2200     };
2201
2202     /**
2203      * @method
2204      * @member Ext
2205      * @alias Ext.Array#each
2206      */
2207     Ext.each = ExtArray.each;
2208
2209     /**
2210      * @method
2211      * @member Ext.Array
2212      * @alias Ext.Array#merge
2213      */
2214     ExtArray.union = ExtArray.merge;
2215
2216     /**
2217      * Old alias to {@link Ext.Array#min}
2218      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2219      * @method
2220      * @member Ext
2221      * @alias Ext.Array#min
2222      */
2223     Ext.min = ExtArray.min;
2224
2225     /**
2226      * Old alias to {@link Ext.Array#max}
2227      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2228      * @method
2229      * @member Ext
2230      * @alias Ext.Array#max
2231      */
2232     Ext.max = ExtArray.max;
2233
2234     /**
2235      * Old alias to {@link Ext.Array#sum}
2236      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2237      * @method
2238      * @member Ext
2239      * @alias Ext.Array#sum
2240      */
2241     Ext.sum = ExtArray.sum;
2242
2243     /**
2244      * Old alias to {@link Ext.Array#mean}
2245      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2246      * @method
2247      * @member Ext
2248      * @alias Ext.Array#mean
2249      */
2250     Ext.mean = ExtArray.mean;
2251
2252     /**
2253      * Old alias to {@link Ext.Array#flatten}
2254      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2255      * @method
2256      * @member Ext
2257      * @alias Ext.Array#flatten
2258      */
2259     Ext.flatten = ExtArray.flatten;
2260
2261     /**
2262      * Old alias to {@link Ext.Array#clean}
2263      * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2264      * @method
2265      * @member Ext
2266      * @alias Ext.Array#clean
2267      */
2268     Ext.clean = ExtArray.clean;
2269
2270     /**
2271      * Old alias to {@link Ext.Array#unique}
2272      * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2273      * @method
2274      * @member Ext
2275      * @alias Ext.Array#unique
2276      */
2277     Ext.unique = ExtArray.unique;
2278
2279     /**
2280      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2281      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2282      * @method
2283      * @member Ext
2284      * @alias Ext.Array#pluck
2285      */
2286     Ext.pluck = ExtArray.pluck;
2287
2288     /**
2289      * @method
2290      * @member Ext
2291      * @alias Ext.Array#toArray
2292      */
2293     Ext.toArray = function() {
2294         return ExtArray.toArray.apply(ExtArray, arguments);
2295     };
2296 })();
2297
2298 /**
2299  * @class Ext.Function
2300  *
2301  * A collection of useful static methods to deal with function callbacks
2302  * @singleton
2303  */
2304 Ext.Function = {
2305
2306     /**
2307      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2308      * which originally accepts 2 arguments for `name` and `value`.
2309      * The wrapped function then allows "flexible" value setting of either:
2310      *
2311      * - `name` and `value` as 2 arguments
2312      * - one single object argument with multiple key - value pairs
2313      *
2314      * For example:
2315      *
2316      *     var setValue = Ext.Function.flexSetter(function(name, value) {
2317      *         this[name] = value;
2318      *     });
2319      *
2320      *     // Afterwards
2321      *     // Setting a single name - value
2322      *     setValue('name1', 'value1');
2323      *
2324      *     // Settings multiple name - value pairs
2325      *     setValue({
2326      *         name1: 'value1',
2327      *         name2: 'value2',
2328      *         name3: 'value3'
2329      *     });
2330      *
2331      * @param {Function} setter
2332      * @returns {Function} flexSetter
2333      */
2334     flexSetter: function(fn) {
2335         return function(a, b) {
2336             var k, i;
2337
2338             if (a === null) {
2339                 return this;
2340             }
2341
2342             if (typeof a !== 'string') {
2343                 for (k in a) {
2344                     if (a.hasOwnProperty(k)) {
2345                         fn.call(this, k, a[k]);
2346                     }
2347                 }
2348
2349                 if (Ext.enumerables) {
2350                     for (i = Ext.enumerables.length; i--;) {
2351                         k = Ext.enumerables[i];
2352                         if (a.hasOwnProperty(k)) {
2353                             fn.call(this, k, a[k]);
2354                         }
2355                     }
2356                 }
2357             } else {
2358                 fn.call(this, a, b);
2359             }
2360
2361             return this;
2362         };
2363     },
2364
2365     /**
2366      * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2367      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2368      *
2369      * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2370      *
2371      * @param {Function} fn The function to delegate.
2372      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2373      * **If omitted, defaults to the browser window.**
2374      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2375      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2376      * if a number the args are inserted at the specified position
2377      * @return {Function} The new function
2378      */
2379     bind: function(fn, scope, args, appendArgs) {
2380         if (arguments.length === 2) {
2381             return function() {
2382                 return fn.apply(scope, arguments);
2383             }
2384         }
2385
2386         var method = fn,
2387             slice = Array.prototype.slice;
2388
2389         return function() {
2390             var callArgs = args || arguments;
2391
2392             if (appendArgs === true) {
2393                 callArgs = slice.call(arguments, 0);
2394                 callArgs = callArgs.concat(args);
2395             }
2396             else if (typeof appendArgs == 'number') {
2397                 callArgs = slice.call(arguments, 0); // copy arguments first
2398                 Ext.Array.insert(callArgs, appendArgs, args);
2399             }
2400
2401             return method.apply(scope || window, callArgs);
2402         };
2403     },
2404
2405     /**
2406      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2407      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2408      * This is especially useful when creating callbacks.
2409      *
2410      * For example:
2411      *
2412      *     var originalFunction = function(){
2413      *         alert(Ext.Array.from(arguments).join(' '));
2414      *     };
2415      *
2416      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2417      *
2418      *     callback(); // alerts 'Hello World'
2419      *     callback('by Me'); // alerts 'Hello World by Me'
2420      *
2421      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2422      *
2423      * @param {Function} fn The original function
2424      * @param {Array} args The arguments to pass to new callback
2425      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2426      * @return {Function} The new callback function
2427      */
2428     pass: function(fn, args, scope) {
2429         if (args) {
2430             args = Ext.Array.from(args);
2431         }
2432
2433         return function() {
2434             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2435         };
2436     },
2437
2438     /**
2439      * Create an alias to the provided method property with name `methodName` of `object`.
2440      * Note that the execution scope will still be bound to the provided `object` itself.
2441      *
2442      * @param {Object/Function} object
2443      * @param {String} methodName
2444      * @return {Function} aliasFn
2445      */
2446     alias: function(object, methodName) {
2447         return function() {
2448             return object[methodName].apply(object, arguments);
2449         };
2450     },
2451
2452     /**
2453      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2454      * the original one is not called. The resulting function returns the results of the original function.
2455      * The passed function is called with the parameters of the original function. Example usage:
2456      *
2457      *     var sayHi = function(name){
2458      *         alert('Hi, ' + name);
2459      *     }
2460      *
2461      *     sayHi('Fred'); // alerts "Hi, Fred"
2462      *
2463      *     // create a new function that validates input without
2464      *     // directly modifying the original function:
2465      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2466      *         return name == 'Brian';
2467      *     });
2468      *
2469      *     sayHiToFriend('Fred');  // no alert
2470      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
2471      *
2472      * @param {Function} origFn The original function.
2473      * @param {Function} newFn The function to call before the original
2474      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2475      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2476      * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
2477      * @return {Function} The new function
2478      */
2479     createInterceptor: function(origFn, newFn, scope, returnValue) {
2480         var method = origFn;
2481         if (!Ext.isFunction(newFn)) {
2482             return origFn;
2483         }
2484         else {
2485             return function() {
2486                 var me = this,
2487                     args = arguments;
2488                 newFn.target = me;
2489                 newFn.method = origFn;
2490                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2491             };
2492         }
2493     },
2494
2495     /**
2496      * Creates a delegate (callback) which, when called, executes after a specific delay.
2497      *
2498      * @param {Function} fn The function which will be called on a delay when the returned function is called.
2499      * Optionally, a replacement (or additional) argument list may be specified.
2500      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2501      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2502      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2503      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2504      * if a number the args are inserted at the specified position.
2505      * @return {Function} A function which, when called, executes the original function after the specified delay.
2506      */
2507     createDelayed: function(fn, delay, scope, args, appendArgs) {
2508         if (scope || args) {
2509             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2510         }
2511         return function() {
2512             var me = this;
2513             setTimeout(function() {
2514                 fn.apply(me, arguments);
2515             }, delay);
2516         };
2517     },
2518
2519     /**
2520      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2521      *
2522      *     var sayHi = function(name){
2523      *         alert('Hi, ' + name);
2524      *     }
2525      *
2526      *     // executes immediately:
2527      *     sayHi('Fred');
2528      *
2529      *     // executes after 2 seconds:
2530      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2531      *
2532      *     // this syntax is sometimes useful for deferring
2533      *     // execution of an anonymous function:
2534      *     Ext.Function.defer(function(){
2535      *         alert('Anonymous');
2536      *     }, 100);
2537      *
2538      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2539      *
2540      * @param {Function} fn The function to defer.
2541      * @param {Number} millis The number of milliseconds for the setTimeout call
2542      * (if less than or equal to 0 the function is executed immediately)
2543      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2544      * **If omitted, defaults to the browser window.**
2545      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2546      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2547      * if a number the args are inserted at the specified position
2548      * @return {Number} The timeout id that can be used with clearTimeout
2549      */
2550     defer: function(fn, millis, obj, args, appendArgs) {
2551         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2552         if (millis > 0) {
2553             return setTimeout(fn, millis);
2554         }
2555         fn();
2556         return 0;
2557     },
2558
2559     /**
2560      * Create a combined function call sequence of the original function + the passed function.
2561      * The resulting function returns the results of the original function.
2562      * The passed function is called with the parameters of the original function. Example usage:
2563      *
2564      *     var sayHi = function(name){
2565      *         alert('Hi, ' + name);
2566      *     }
2567      *
2568      *     sayHi('Fred'); // alerts "Hi, Fred"
2569      *
2570      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2571      *         alert('Bye, ' + name);
2572      *     });
2573      *
2574      *     sayGoodbye('Fred'); // both alerts show
2575      *
2576      * @param {Function} origFn The original function.
2577      * @param {Function} newFn The function to sequence
2578      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2579      * If omitted, defaults to the scope in which the original function is called or the browser window.
2580      * @return {Function} The new function
2581      */
2582     createSequence: function(origFn, newFn, scope) {
2583         if (!Ext.isFunction(newFn)) {
2584             return origFn;
2585         }
2586         else {
2587             return function() {
2588                 var retval = origFn.apply(this || window, arguments);
2589                 newFn.apply(scope || this || window, arguments);
2590                 return retval;
2591             };
2592         }
2593     },
2594
2595     /**
2596      * Creates a delegate function, optionally with a bound scope which, when called, buffers
2597      * the execution of the passed function for the configured number of milliseconds.
2598      * If called again within that period, the impending invocation will be canceled, and the
2599      * timeout period will begin again.
2600      *
2601      * @param {Function} fn The function to invoke on a buffered timer.
2602      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2603      * function.
2604      * @param {Object} scope (optional) The scope (`this` reference) in which
2605      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2606      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2607      * passed by the caller.
2608      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2609      */
2610     createBuffered: function(fn, buffer, scope, args) {
2611         return function(){
2612             var timerId;
2613             return function() {
2614                 var me = this;
2615                 if (timerId) {
2616                     clearTimeout(timerId);
2617                     timerId = null;
2618                 }
2619                 timerId = setTimeout(function(){
2620                     fn.apply(scope || me, args || arguments);
2621                 }, buffer);
2622             };
2623         }();
2624     },
2625
2626     /**
2627      * Creates a throttled version of the passed function which, when called repeatedly and
2628      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2629      * previous invocation.
2630      *
2631      * This is useful for wrapping functions which may be called repeatedly, such as
2632      * a handler of a mouse move event when the processing is expensive.
2633      *
2634      * @param {Function} fn The function to execute at a regular time interval.
2635      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2636      * @param {Object} scope (optional) The scope (`this` reference) in which
2637      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2638      * @returns {Function} A function which invokes the passed function at the specified interval.
2639      */
2640     createThrottled: function(fn, interval, scope) {
2641         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2642             fn.apply(scope || this, lastArgs);
2643             lastCallTime = new Date().getTime();
2644         };
2645
2646         return function() {
2647             elapsed = new Date().getTime() - lastCallTime;
2648             lastArgs = arguments;
2649
2650             clearTimeout(timer);
2651             if (!lastCallTime || (elapsed >= interval)) {
2652                 execute();
2653             } else {
2654                 timer = setTimeout(execute, interval - elapsed);
2655             }
2656         };
2657     },
2658
2659     /**
2660      * Adds behavior to an existing method that is executed before the
2661      * original behavior of the function.  For example:
2662      * 
2663      *     var soup = {
2664      *         contents: [],
2665      *         add: function(ingredient) {
2666      *             this.contents.push(ingredient);
2667      *         }
2668      *     };
2669      *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
2670      *         if (!this.contents.length && ingredient !== "water") {
2671      *             // Always add water to start with
2672      *             this.contents.push("water");
2673      *         }
2674      *     });
2675      *     soup.add("onions");
2676      *     soup.add("salt");
2677      *     soup.contents; // will contain: water, onions, salt
2678      * 
2679      * @param {Object} object The target object
2680      * @param {String} methodName Name of the method to override
2681      * @param {Function} fn Function with the new behavior.  It will
2682      * be called with the same arguments as the original method.  The
2683      * return value of this function will be the return value of the
2684      * new method.
2685      * @return {Function} The new function just created.
2686      */
2687     interceptBefore: function(object, methodName, fn) {
2688         var method = object[methodName] || Ext.emptyFn;
2689
2690         return object[methodName] = function() {
2691             var ret = fn.apply(this, arguments);
2692             method.apply(this, arguments);
2693
2694             return ret;
2695         };
2696     },
2697
2698     /**
2699      * Adds behavior to an existing method that is executed after the
2700      * original behavior of the function.  For example:
2701      * 
2702      *     var soup = {
2703      *         contents: [],
2704      *         add: function(ingredient) {
2705      *             this.contents.push(ingredient);
2706      *         }
2707      *     };
2708      *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
2709      *         // Always add a bit of extra salt
2710      *         this.contents.push("salt");
2711      *     });
2712      *     soup.add("water");
2713      *     soup.add("onions");
2714      *     soup.contents; // will contain: water, salt, onions, salt
2715      * 
2716      * @param {Object} object The target object
2717      * @param {String} methodName Name of the method to override
2718      * @param {Function} fn Function with the new behavior.  It will
2719      * be called with the same arguments as the original method.  The
2720      * return value of this function will be the return value of the
2721      * new method.
2722      * @return {Function} The new function just created.
2723      */
2724     interceptAfter: function(object, methodName, fn) {
2725         var method = object[methodName] || Ext.emptyFn;
2726
2727         return object[methodName] = function() {
2728             method.apply(this, arguments);
2729             return fn.apply(this, arguments);
2730         };
2731     }
2732 };
2733
2734 /**
2735  * @method
2736  * @member Ext
2737  * @alias Ext.Function#defer
2738  */
2739 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2740
2741 /**
2742  * @method
2743  * @member Ext
2744  * @alias Ext.Function#pass
2745  */
2746 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2747
2748 /**
2749  * @method
2750  * @member Ext
2751  * @alias Ext.Function#bind
2752  */
2753 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2754
2755 /**
2756  * @author Jacky Nguyen <jacky@sencha.com>
2757  * @docauthor Jacky Nguyen <jacky@sencha.com>
2758  * @class Ext.Object
2759  *
2760  * A collection of useful static methods to deal with objects.
2761  *
2762  * @singleton
2763  */
2764
2765 (function() {
2766
2767 var ExtObject = Ext.Object = {
2768
2769     /**
2770      * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
2771      * query strings. For example:
2772      *
2773      *     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2774      *
2775      *     // objects then equals:
2776      *     [
2777      *         { name: 'hobbies', value: 'reading' },
2778      *         { name: 'hobbies', value: 'cooking' },
2779      *         { name: 'hobbies', value: 'swimming' },
2780      *     ];
2781      *
2782      *     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2783      *         day: 3,
2784      *         month: 8,
2785      *         year: 1987,
2786      *         extra: {
2787      *             hour: 4
2788      *             minute: 30
2789      *         }
2790      *     }, true); // Recursive
2791      *
2792      *     // objects then equals:
2793      *     [
2794      *         { name: 'dateOfBirth[day]', value: 3 },
2795      *         { name: 'dateOfBirth[month]', value: 8 },
2796      *         { name: 'dateOfBirth[year]', value: 1987 },
2797      *         { name: 'dateOfBirth[extra][hour]', value: 4 },
2798      *         { name: 'dateOfBirth[extra][minute]', value: 30 },
2799      *     ];
2800      *
2801      * @param {String} name
2802      * @param {Object/Array} value
2803      * @param {Boolean} [recursive=false] True to traverse object recursively
2804      * @return {Array}
2805      */
2806     toQueryObjects: function(name, value, recursive) {
2807         var self = ExtObject.toQueryObjects,
2808             objects = [],
2809             i, ln;
2810
2811         if (Ext.isArray(value)) {
2812             for (i = 0, ln = value.length; i < ln; i++) {
2813                 if (recursive) {
2814                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2815                 }
2816                 else {
2817                     objects.push({
2818                         name: name,
2819                         value: value[i]
2820                     });
2821                 }
2822             }
2823         }
2824         else if (Ext.isObject(value)) {
2825             for (i in value) {
2826                 if (value.hasOwnProperty(i)) {
2827                     if (recursive) {
2828                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2829                     }
2830                     else {
2831                         objects.push({
2832                             name: name,
2833                             value: value[i]
2834                         });
2835                     }
2836                 }
2837             }
2838         }
2839         else {
2840             objects.push({
2841                 name: name,
2842                 value: value
2843             });
2844         }
2845
2846         return objects;
2847     },
2848
2849     /**
2850      * Takes an object and converts it to an encoded query string.
2851      *
2852      * Non-recursive:
2853      *
2854      *     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2855      *     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2856      *     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2857      *     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2858      *     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2859      *
2860      * Recursive:
2861      *
2862      *     Ext.Object.toQueryString({
2863      *         username: 'Jacky',
2864      *         dateOfBirth: {
2865      *             day: 1,
2866      *             month: 2,
2867      *             year: 1911
2868      *         },
2869      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2870      *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2871      *     // username=Jacky
2872      *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2873      *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2874      *
2875      * @param {Object} object The object to encode
2876      * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
2877      * (PHP / Ruby on Rails servers and similar).
2878      * @return {String} queryString
2879      */
2880     toQueryString: function(object, recursive) {
2881         var paramObjects = [],
2882             params = [],
2883             i, j, ln, paramObject, value;
2884
2885         for (i in object) {
2886             if (object.hasOwnProperty(i)) {
2887                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2888             }
2889         }
2890
2891         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2892             paramObject = paramObjects[j];
2893             value = paramObject.value;
2894
2895             if (Ext.isEmpty(value)) {
2896                 value = '';
2897             }
2898             else if (Ext.isDate(value)) {
2899                 value = Ext.Date.toString(value);
2900             }
2901
2902             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2903         }
2904
2905         return params.join('&');
2906     },
2907
2908     /**
2909      * Converts a query string back into an object.
2910      *
2911      * Non-recursive:
2912      *
2913      *     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2914      *     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2915      *     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2916      *     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2917      *
2918      * Recursive:
2919      *
2920      *       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);
2921      *     // returns
2922      *     {
2923      *         username: 'Jacky',
2924      *         dateOfBirth: {
2925      *             day: '1',
2926      *             month: '2',
2927      *             year: '1911'
2928      *         },
2929      *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2930      *     }
2931      *
2932      * @param {String} queryString The query string to decode
2933      * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
2934      * PHP / Ruby on Rails servers and similar.
2935      * @return {Object}
2936      */
2937     fromQueryString: function(queryString, recursive) {
2938         var parts = queryString.replace(/^\?/, '').split('&'),
2939             object = {},
2940             temp, components, name, value, i, ln,
2941             part, j, subLn, matchedKeys, matchedName,
2942             keys, key, nextKey;
2943
2944         for (i = 0, ln = parts.length; i < ln; i++) {
2945             part = parts[i];
2946
2947             if (part.length > 0) {
2948                 components = part.split('=');
2949                 name = decodeURIComponent(components[0]);
2950                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2951
2952                 if (!recursive) {
2953                     if (object.hasOwnProperty(name)) {
2954                         if (!Ext.isArray(object[name])) {
2955                             object[name] = [object[name]];
2956                         }
2957
2958                         object[name].push(value);
2959                     }
2960                     else {
2961                         object[name] = value;
2962                     }
2963                 }
2964                 else {
2965                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2966                     matchedName = name.match(/^([^\[]+)/);
2967
2968
2969                     name = matchedName[0];
2970                     keys = [];
2971
2972                     if (matchedKeys === null) {
2973                         object[name] = value;
2974                         continue;
2975                     }
2976
2977                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2978                         key = matchedKeys[j];
2979                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2980                         keys.push(key);
2981                     }
2982
2983                     keys.unshift(name);
2984
2985                     temp = object;
2986
2987                     for (j = 0, subLn = keys.length; j < subLn; j++) {
2988                         key = keys[j];
2989
2990                         if (j === subLn - 1) {
2991                             if (Ext.isArray(temp) && key === '') {
2992                                 temp.push(value);
2993                             }
2994                             else {
2995                                 temp[key] = value;
2996                             }
2997                         }
2998                         else {
2999                             if (temp[key] === undefined || typeof temp[key] === 'string') {
3000                                 nextKey = keys[j+1];
3001
3002                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
3003                             }
3004
3005                             temp = temp[key];
3006                         }
3007                     }
3008                 }
3009             }
3010         }
3011
3012         return object;
3013     },
3014
3015     /**
3016      * Iterates through an object and invokes the given callback function for each iteration.
3017      * The iteration can be stopped by returning `false` in the callback function. For example:
3018      *
3019      *     var person = {
3020      *         name: 'Jacky'
3021      *         hairColor: 'black'
3022      *         loves: ['food', 'sleeping', 'wife']
3023      *     };
3024      *
3025      *     Ext.Object.each(person, function(key, value, myself) {
3026      *         console.log(key + ":" + value);
3027      *
3028      *         if (key === 'hairColor') {
3029      *             return false; // stop the iteration
3030      *         }
3031      *     });
3032      *
3033      * @param {Object} object The object to iterate
3034      * @param {Function} fn The callback function.
3035      * @param {String} fn.key
3036      * @param {Object} fn.value
3037      * @param {Object} fn.object The object itself
3038      * @param {Object} [scope] The execution scope (`this`) of the callback function
3039      */
3040     each: function(object, fn, scope) {
3041         for (var property in object) {
3042             if (object.hasOwnProperty(property)) {
3043                 if (fn.call(scope || object, property, object[property], object) === false) {
3044                     return;
3045                 }
3046             }
3047         }
3048     },
3049
3050     /**
3051      * Merges any number of objects recursively without referencing them or their children.
3052      *
3053      *     var extjs = {
3054      *         companyName: 'Ext JS',
3055      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
3056      *         isSuperCool: true
3057      *         office: {
3058      *             size: 2000,
3059      *             location: 'Palo Alto',
3060      *             isFun: true
3061      *         }
3062      *     };
3063      *
3064      *     var newStuff = {
3065      *         companyName: 'Sencha Inc.',
3066      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3067      *         office: {
3068      *             size: 40000,
3069      *             location: 'Redwood City'
3070      *         }
3071      *     };
3072      *
3073      *     var sencha = Ext.Object.merge(extjs, newStuff);
3074      *
3075      *     // extjs and sencha then equals to
3076      *     {
3077      *         companyName: 'Sencha Inc.',
3078      *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3079      *         isSuperCool: true
3080      *         office: {
3081      *             size: 30000,
3082      *             location: 'Redwood City'
3083      *             isFun: true
3084      *         }
3085      *     }
3086      *
3087      * @param {Object...} object Any number of objects to merge.
3088      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3089      */
3090     merge: function(source, key, value) {
3091         if (typeof key === 'string') {
3092             if (value && value.constructor === Object) {
3093                 if (source[key] && source[key].constructor === Object) {
3094                     ExtObject.merge(source[key], value);
3095                 }
3096                 else {
3097                     source[key] = Ext.clone(value);
3098                 }
3099             }
3100             else {
3101                 source[key] = value;
3102             }
3103
3104             return source;
3105         }
3106
3107         var i = 1,
3108             ln = arguments.length,
3109             object, property;
3110
3111         for (; i < ln; i++) {
3112             object = arguments[i];
3113
3114             for (property in object) {
3115                 if (object.hasOwnProperty(property)) {
3116                     ExtObject.merge(source, property, object[property]);
3117                 }
3118             }
3119         }
3120
3121         return source;
3122     },
3123
3124     /**
3125      * Returns the first matching key corresponding to the given value.
3126      * If no matching value is found, null is returned.
3127      *
3128      *     var person = {
3129      *         name: 'Jacky',
3130      *         loves: 'food'
3131      *     };
3132      *
3133      *     alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
3134      *
3135      * @param {Object} object
3136      * @param {Object} value The value to find
3137      */
3138     getKey: function(object, value) {
3139         for (var property in object) {
3140             if (object.hasOwnProperty(property) && object[property] === value) {
3141                 return property;
3142             }
3143         }
3144
3145         return null;
3146     },
3147
3148     /**
3149      * Gets all values of the given object as an array.
3150      *
3151      *     var values = Ext.Object.getValues({
3152      *         name: 'Jacky',
3153      *         loves: 'food'
3154      *     }); // ['Jacky', 'food']
3155      *
3156      * @param {Object} object
3157      * @return {Array} An array of values from the object
3158      */
3159     getValues: function(object) {
3160         var values = [],
3161             property;
3162
3163         for (property in object) {
3164             if (object.hasOwnProperty(property)) {
3165                 values.push(object[property]);
3166             }
3167         }
3168
3169         return values;
3170     },
3171
3172     /**
3173      * Gets all keys of the given object as an array.
3174      *
3175      *     var values = Ext.Object.getKeys({
3176      *         name: 'Jacky',
3177      *         loves: 'food'
3178      *     }); // ['name', 'loves']
3179      *
3180      * @param {Object} object
3181      * @return {String[]} An array of keys from the object
3182      * @method
3183      */
3184     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3185         var keys = [],
3186             property;
3187
3188         for (property in object) {
3189             if (object.hasOwnProperty(property)) {
3190                 keys.push(property);
3191             }
3192         }
3193
3194         return keys;
3195     },
3196
3197     /**
3198      * Gets the total number of this object's own properties
3199      *
3200      *     var size = Ext.Object.getSize({
3201      *         name: 'Jacky',
3202      *         loves: 'food'
3203      *     }); // size equals 2
3204      *
3205      * @param {Object} object
3206      * @return {Number} size
3207      */
3208     getSize: function(object) {
3209         var size = 0,
3210             property;
3211
3212         for (property in object) {
3213             if (object.hasOwnProperty(property)) {
3214                 size++;
3215             }
3216         }
3217
3218         return size;
3219     }
3220 };
3221
3222
3223 /**
3224  * A convenient alias method for {@link Ext.Object#merge}.
3225  *
3226  * @member Ext
3227  * @method merge
3228  * @alias Ext.Object#merge
3229  */
3230 Ext.merge = Ext.Object.merge;
3231
3232 /**
3233  * Alias for {@link Ext.Object#toQueryString}.
3234  *
3235  * @member Ext
3236  * @method urlEncode
3237  * @alias Ext.Object#toQueryString
3238  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
3239  */
3240 Ext.urlEncode = function() {
3241     var args = Ext.Array.from(arguments),
3242         prefix = '';
3243
3244     // Support for the old `pre` argument
3245     if ((typeof args[1] === 'string')) {
3246         prefix = args[1] + '&';
3247         args[1] = false;
3248     }
3249
3250     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3251 };
3252
3253 /**
3254  * Alias for {@link Ext.Object#fromQueryString}.
3255  *
3256  * @member Ext
3257  * @method urlDecode
3258  * @alias Ext.Object#fromQueryString
3259  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
3260  */
3261 Ext.urlDecode = function() {
3262     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3263 };
3264
3265 })();
3266
3267 /**
3268  * @class Ext.Date
3269  * A set of useful static methods to deal with date
3270  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3271  * this object for convenience
3272  *
3273  * The date parsing and formatting syntax contains a subset of
3274  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3275  * supported will provide results equivalent to their PHP versions.
3276  *
3277  * The following is a list of all currently supported formats:
3278  * <pre class="">
3279 Format  Description                                                               Example returned values
3280 ------  -----------------------------------------------------------------------   -----------------------
3281   d     Day of the month, 2 digits with leading zeros                             01 to 31
3282   D     A short textual representation of the day of the week                     Mon to Sun
3283   j     Day of the month without leading zeros                                    1 to 31
3284   l     A full textual representation of the day of the week                      Sunday to Saturday
3285   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
3286   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
3287   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
3288   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
3289   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
3290   F     A full textual representation of a month, such as January or March        January to December
3291   m     Numeric representation of a month, with leading zeros                     01 to 12
3292   M     A short textual representation of a month                                 Jan to Dec
3293   n     Numeric representation of a month, without leading zeros                  1 to 12
3294   t     Number of days in the given month                                         28 to 31
3295   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
3296   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
3297         belongs to the previous or next year, that year is used instead)
3298   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
3299   y     A two digit representation of a year                                      Examples: 99 or 03
3300   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
3301   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
3302   g     12-hour format of an hour without leading zeros                           1 to 12
3303   G     24-hour format of an hour without leading zeros                           0 to 23
3304   h     12-hour format of an hour with leading zeros                              01 to 12
3305   H     24-hour format of an hour with leading zeros                              00 to 23
3306   i     Minutes, with leading zeros                                               00 to 59
3307   s     Seconds, with leading zeros                                               00 to 59
3308   u     Decimal fraction of a second                                              Examples:
3309         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
3310                                                                                   100 (i.e. 0.100s) or
3311                                                                                   999 (i.e. 0.999s) or
3312                                                                                   999876543210 (i.e. 0.999876543210s)
3313   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
3314   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
3315   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
3316   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
3317   c     ISO 8601 date
3318         Notes:                                                                    Examples:
3319         1) If unspecified, the month / day defaults to the current month / day,   1991 or
3320            the time defaults to midnight, while the timezone defaults to the      1992-10 or
3321            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3322            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
3323            are optional.                                                          1995-07-18T17:21:28-02:00 or
3324         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
3325            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
3326            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
3327         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
3328         date-time granularity which are supported, or see                         2000-02-13T21:25:33
3329         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
3330   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
3331   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3332                                                                                   \/Date(1238606590509+0800)\/
3333 </pre>
3334  *
3335  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3336  * <pre><code>
3337 // Sample date:
3338 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3339
3340 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3341 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
3342 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
3343 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
3344 </code></pre>
3345  *
3346  * Here are some standard date/time patterns that you might find helpful.  They
3347  * are not part of the source of Ext.Date, but to use them you can simply copy this
3348  * block of code into any script that is included after Ext.Date and they will also become
3349  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
3350  * <pre><code>
3351 Ext.Date.patterns = {
3352     ISO8601Long:"Y-m-d H:i:s",
3353     ISO8601Short:"Y-m-d",
3354     ShortDate: "n/j/Y",
3355     LongDate: "l, F d, Y",
3356     FullDateTime: "l, F d, Y g:i:s A",
3357     MonthDay: "F d",
3358     ShortTime: "g:i A",
3359     LongTime: "g:i:s A",
3360     SortableDateTime: "Y-m-d\\TH:i:s",
3361     UniversalSortableDateTime: "Y-m-d H:i:sO",
3362     YearMonth: "F, Y"
3363 };
3364 </code></pre>
3365  *
3366  * Example usage:
3367  * <pre><code>
3368 var dt = new Date();
3369 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3370 </code></pre>
3371  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3372  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3373  * @singleton
3374  */
3375
3376 /*
3377  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3378  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3379  * They generate precompiled functions from format patterns instead of parsing and
3380  * processing each pattern every time a date is formatted. These functions are available
3381  * on every Date object.
3382  */
3383
3384 (function() {
3385
3386 // create private copy of Ext's Ext.util.Format.format() method
3387 // - to remove unnecessary dependency
3388 // - to resolve namespace conflict with MS-Ajax's implementation
3389 function xf(format) {
3390     var args = Array.prototype.slice.call(arguments, 1);
3391     return format.replace(/\{(\d+)\}/g, function(m, i) {
3392         return args[i];
3393     });
3394 }
3395
3396 Ext.Date = {
3397     /**
3398      * Returns the current timestamp
3399      * @return {Date} The current timestamp
3400      * @method
3401      */
3402     now: Date.now || function() {
3403         return +new Date();
3404     },
3405
3406     /**
3407      * @private
3408      * Private for now
3409      */
3410     toString: function(date) {
3411         var pad = Ext.String.leftPad;
3412
3413         return date.getFullYear() + "-"
3414             + pad(date.getMonth() + 1, 2, '0') + "-"
3415             + pad(date.getDate(), 2, '0') + "T"
3416             + pad(date.getHours(), 2, '0') + ":"
3417             + pad(date.getMinutes(), 2, '0') + ":"
3418             + pad(date.getSeconds(), 2, '0');
3419     },
3420
3421     /**
3422      * Returns the number of milliseconds between two dates
3423      * @param {Date} dateA The first date
3424      * @param {Date} dateB (optional) The second date, defaults to now
3425      * @return {Number} The difference in milliseconds
3426      */
3427     getElapsed: function(dateA, dateB) {
3428         return Math.abs(dateA - (dateB || new Date()));
3429     },
3430
3431     /**
3432      * Global flag which determines if strict date parsing should be used.
3433      * Strict date parsing will not roll-over invalid dates, which is the
3434      * default behaviour of javascript Date objects.
3435      * (see {@link #parse} for more information)
3436      * Defaults to <tt>false</tt>.
3437      * @type Boolean
3438     */
3439     useStrict: false,
3440
3441     // private
3442     formatCodeToRegex: function(character, currentGroup) {
3443         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3444         var p = utilDate.parseCodes[character];
3445
3446         if (p) {
3447           p = typeof p == 'function'? p() : p;
3448           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3449         }
3450
3451         return p ? Ext.applyIf({
3452           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3453         }, p) : {
3454             g: 0,
3455             c: null,
3456             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3457         };
3458     },
3459
3460     /**
3461      * <p>An object hash in which each property is a date parsing function. The property name is the
3462      * format string which that function parses.</p>
3463      * <p>This object is automatically populated with date parsing functions as
3464      * date formats are requested for Ext standard formatting strings.</p>
3465      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3466      * may be used as a format string to {@link #parse}.<p>
3467      * <p>Example:</p><pre><code>
3468 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3469 </code></pre>
3470      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3471      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3472      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3473      * (i.e. prevent javascript Date "rollover") (The default must be false).
3474      * Invalid date strings should return null when parsed.</div></li>
3475      * </ul></div></p>
3476      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3477      * formatting function must be placed into the {@link #formatFunctions} property.
3478      * @property parseFunctions
3479      * @type Object
3480      */
3481     parseFunctions: {
3482         "MS": function(input, strict) {
3483             // note: the timezone offset is ignored since the MS Ajax server sends
3484             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3485             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3486             var r = (input || '').match(re);
3487             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3488         }
3489     },
3490     parseRegexes: [],
3491
3492     /**
3493      * <p>An object hash in which each property is a date formatting function. The property name is the
3494      * format string which corresponds to the produced formatted date string.</p>
3495      * <p>This object is automatically populated with date formatting functions as
3496      * date formats are requested for Ext standard formatting strings.</p>
3497      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3498      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3499 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3500 </code></pre>
3501      * <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>
3502      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3503      * </ul></div></p>
3504      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3505      * parsing function must be placed into the {@link #parseFunctions} property.
3506      * @property formatFunctions
3507      * @type Object
3508      */
3509     formatFunctions: {
3510         "MS": function() {
3511             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3512             return '\\/Date(' + this.getTime() + ')\\/';
3513         }
3514     },
3515
3516     y2kYear : 50,
3517
3518     /**
3519      * Date interval constant
3520      * @type String
3521      */
3522     MILLI : "ms",
3523
3524     /**
3525      * Date interval constant
3526      * @type String
3527      */
3528     SECOND : "s",
3529
3530     /**
3531      * Date interval constant
3532      * @type String
3533      */
3534     MINUTE : "mi",
3535
3536     /** Date interval constant
3537      * @type String
3538      */
3539     HOUR : "h",
3540
3541     /**
3542      * Date interval constant
3543      * @type String
3544      */
3545     DAY : "d",
3546
3547     /**
3548      * Date interval constant
3549      * @type String
3550      */
3551     MONTH : "mo",
3552
3553     /**
3554      * Date interval constant
3555      * @type String
3556      */
3557     YEAR : "y",
3558
3559     /**
3560      * <p>An object hash containing default date values used during date parsing.</p>
3561      * <p>The following properties are available:<div class="mdetail-params"><ul>
3562      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3563      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3564      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3565      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3566      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3567      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3568      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3569      * </ul></div></p>
3570      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3571      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3572      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3573      * It is the responsiblity of the developer to account for this.</b></p>
3574      * Example Usage:
3575      * <pre><code>
3576 // set default day value to the first day of the month
3577 Ext.Date.defaults.d = 1;
3578
3579 // parse a February date string containing only year and month values.
3580 // setting the default day value to 1 prevents weird date rollover issues
3581 // when attempting to parse the following date string on, for example, March 31st 2009.
3582 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3583 </code></pre>
3584      * @property defaults
3585      * @type Object
3586      */
3587     defaults: {},
3588
3589     /**
3590      * @property {String[]} dayNames
3591      * An array of textual day names.
3592      * Override these values for international dates.
3593      * Example:
3594      * <pre><code>
3595 Ext.Date.dayNames = [
3596     'SundayInYourLang',
3597     'MondayInYourLang',
3598     ...
3599 ];
3600 </code></pre>
3601      */
3602     dayNames : [
3603         "Sunday",
3604         "Monday",
3605         "Tuesday",
3606         "Wednesday",
3607         "Thursday",
3608         "Friday",
3609         "Saturday"
3610     ],
3611
3612     /**
3613      * @property {String[]} monthNames
3614      * An array of textual month names.
3615      * Override these values for international dates.
3616      * Example:
3617      * <pre><code>
3618 Ext.Date.monthNames = [
3619     'JanInYourLang',
3620     'FebInYourLang',
3621     ...
3622 ];
3623 </code></pre>
3624      */
3625     monthNames : [
3626         "January",
3627         "February",
3628         "March",
3629         "April",
3630         "May",
3631         "June",
3632         "July",
3633         "August",
3634         "September",
3635         "October",
3636         "November",
3637         "December"
3638     ],
3639
3640     /**
3641      * @property {Object} monthNumbers
3642      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3643      * Override these values for international dates.
3644      * Example:
3645      * <pre><code>
3646 Ext.Date.monthNumbers = {
3647     'ShortJanNameInYourLang':0,
3648     'ShortFebNameInYourLang':1,
3649     ...
3650 };
3651 </code></pre>
3652      */
3653     monthNumbers : {
3654         Jan:0,
3655         Feb:1,
3656         Mar:2,
3657         Apr:3,
3658         May:4,
3659         Jun:5,
3660         Jul:6,
3661         Aug:7,
3662         Sep:8,
3663         Oct:9,
3664         Nov:10,
3665         Dec:11
3666     },
3667     /**
3668      * @property {String} defaultFormat
3669      * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3670      * and {@link Ext.util.Format#date} functions use.  See {@link Ext.Date} for details.</p>
3671      * <p>This may be overridden in a locale file.</p>
3672      */
3673     defaultFormat : "m/d/Y",
3674     /**
3675      * Get the short month name for the given month number.
3676      * Override this function for international dates.
3677      * @param {Number} month A zero-based javascript month number.
3678      * @return {String} The short month name.
3679      */
3680     getShortMonthName : function(month) {
3681         return utilDate.monthNames[month].substring(0, 3);
3682     },
3683
3684     /**
3685      * Get the short day name for the given day number.
3686      * Override this function for international dates.
3687      * @param {Number} day A zero-based javascript day number.
3688      * @return {String} The short day name.
3689      */
3690     getShortDayName : function(day) {
3691         return utilDate.dayNames[day].substring(0, 3);
3692     },
3693
3694     /**
3695      * Get the zero-based javascript month number for the given short/full month name.
3696      * Override this function for international dates.
3697      * @param {String} name The short/full month name.
3698      * @return {Number} The zero-based javascript month number.
3699      */
3700     getMonthNumber : function(name) {
3701         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3702         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3703     },
3704
3705     /**
3706      * Checks if the specified format contains hour information
3707      * @param {String} format The format to check
3708      * @return {Boolean} True if the format contains hour information
3709      * @method
3710      */
3711     formatContainsHourInfo : (function(){
3712         var stripEscapeRe = /(\\.)/g,
3713             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3714         return function(format){
3715             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3716         };
3717     })(),
3718
3719     /**
3720      * Checks if the specified format contains information about
3721      * anything other than the time.
3722      * @param {String} format The format to check
3723      * @return {Boolean} True if the format contains information about
3724      * date/day information.
3725      * @method
3726      */
3727     formatContainsDateInfo : (function(){
3728         var stripEscapeRe = /(\\.)/g,
3729             dateInfoRe = /([djzmnYycU]|MS)/;
3730
3731         return function(format){
3732             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3733         };
3734     })(),
3735
3736     /**
3737      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3738      * Formatting functions are strings (or functions which return strings) which
3739      * will return the appropriate value when evaluated in the context of the Date object
3740      * from which the {@link #format} method is called.
3741      * Add to / override these mappings for custom date formatting.
3742      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3743      * Example:
3744      * <pre><code>
3745 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3746 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3747 </code></pre>
3748      * @type Object
3749      */
3750     formatCodes : {
3751         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3752         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3753         j: "this.getDate()",
3754         l: "Ext.Date.dayNames[this.getDay()]",
3755         N: "(this.getDay() ? this.getDay() : 7)",
3756         S: "Ext.Date.getSuffix(this)",
3757         w: "this.getDay()",
3758         z: "Ext.Date.getDayOfYear(this)",
3759         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3760         F: "Ext.Date.monthNames[this.getMonth()]",
3761         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3762         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3763         n: "(this.getMonth() + 1)",
3764         t: "Ext.Date.getDaysInMonth(this)",
3765         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3766         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3767         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3768         y: "('' + this.getFullYear()).substring(2, 4)",
3769         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3770         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3771         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3772         G: "this.getHours()",
3773         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3774         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3775         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3776         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3777         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3778         O: "Ext.Date.getGMTOffset(this)",
3779         P: "Ext.Date.getGMTOffset(this, true)",
3780         T: "Ext.Date.getTimezone(this)",
3781         Z: "(this.getTimezoneOffset() * -60)",
3782
3783         c: function() { // ISO-8601 -- GMT format
3784             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3785                 var e = c.charAt(i);
3786                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3787             }
3788             return code.join(" + ");
3789         },
3790         /*
3791         c: function() { // ISO-8601 -- UTC format
3792             return [
3793               "this.getUTCFullYear()", "'-'",
3794               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3795               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3796               "'T'",
3797               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3798               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3799               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3800               "'Z'"
3801             ].join(" + ");
3802         },
3803         */
3804
3805         U: "Math.round(this.getTime() / 1000)"
3806     },
3807
3808     /**
3809      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3810      * @param {Number} year 4-digit year
3811      * @param {Number} month 1-based month-of-year
3812      * @param {Number} day Day of month
3813      * @param {Number} hour (optional) Hour
3814      * @param {Number} minute (optional) Minute
3815      * @param {Number} second (optional) Second
3816      * @param {Number} millisecond (optional) Millisecond
3817      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3818      */
3819     isValid : function(y, m, d, h, i, s, ms) {
3820         // setup defaults
3821         h = h || 0;
3822         i = i || 0;
3823         s = s || 0;
3824         ms = ms || 0;
3825
3826         // Special handling for year < 100
3827         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3828
3829         return y == dt.getFullYear() &&
3830             m == dt.getMonth() + 1 &&
3831             d == dt.getDate() &&
3832             h == dt.getHours() &&
3833             i == dt.getMinutes() &&
3834             s == dt.getSeconds() &&
3835             ms == dt.getMilliseconds();
3836     },
3837
3838     /**
3839      * Parses the passed string using the specified date format.
3840      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3841      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3842      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3843      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3844      * Keep in mind that the input date string must precisely match the specified format string
3845      * in order for the parse operation to be successful (failed parse operations return a null value).
3846      * <p>Example:</p><pre><code>
3847 //dt = Fri May 25 2007 (current date)
3848 var dt = new Date();
3849
3850 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3851 dt = Ext.Date.parse("2006", "Y");
3852
3853 //dt = Sun Jan 15 2006 (all date parts specified)
3854 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3855
3856 //dt = Sun Jan 15 2006 15:20:01
3857 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3858
3859 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3860 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3861 </code></pre>
3862      * @param {String} input The raw date string.
3863      * @param {String} format The expected date string format.
3864      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3865                         (defaults to false). Invalid date strings will return null when parsed.
3866      * @return {Date} The parsed Date.
3867      */
3868     parse : function(input, format, strict) {
3869         var p = utilDate.parseFunctions;
3870         if (p[format] == null) {
3871             utilDate.createParser(format);
3872         }
3873         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3874     },
3875
3876     // Backwards compat
3877     parseDate: function(input, format, strict){
3878         return utilDate.parse(input, format, strict);
3879     },
3880
3881
3882     // private
3883     getFormatCode : function(character) {
3884         var f = utilDate.formatCodes[character];
3885
3886         if (f) {
3887           f = typeof f == 'function'? f() : f;
3888           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3889         }
3890
3891         // note: unknown characters are treated as literals
3892         return f || ("'" + Ext.String.escape(character) + "'");
3893     },
3894
3895     // private
3896     createFormat : function(format) {
3897         var code = [],
3898             special = false,
3899             ch = '';
3900
3901         for (var i = 0; i < format.length; ++i) {
3902             ch = format.charAt(i);
3903             if (!special && ch == "\\") {
3904                 special = true;
3905             } else if (special) {
3906                 special = false;
3907                 code.push("'" + Ext.String.escape(ch) + "'");
3908             } else {
3909                 code.push(utilDate.getFormatCode(ch));
3910             }
3911         }
3912         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3913     },
3914
3915     // private
3916     createParser : (function() {
3917         var code = [
3918             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3919                 "def = Ext.Date.defaults,",
3920                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3921
3922             "if(results){",
3923                 "{1}",
3924
3925                 "if(u != null){", // i.e. unix time is defined
3926                     "v = new Date(u * 1000);", // give top priority to UNIX time
3927                 "}else{",
3928                     // create Date object representing midnight of the current day;
3929                     // this will provide us with our date defaults
3930                     // (note: clearTime() handles Daylight Saving Time automatically)
3931                     "dt = Ext.Date.clearTime(new Date);",
3932
3933                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3934                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3935                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3936                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3937
3938                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3939                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3940                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3941                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3942                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3943
3944                     "if(z >= 0 && y >= 0){",
3945                         // both the year and zero-based day of year are defined and >= 0.
3946                         // these 2 values alone provide sufficient info to create a full date object
3947
3948                         // create Date object representing January 1st for the given year
3949                         // handle years < 100 appropriately
3950                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3951
3952                         // then add day of year, checking for Date "rollover" if necessary
3953                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3954                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3955                         "v = null;", // invalid date, so return null
3956                     "}else{",
3957                         // plain old Date object
3958                         // handle years < 100 properly
3959                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3960                     "}",
3961                 "}",
3962             "}",
3963
3964             "if(v){",
3965                 // favour UTC offset over GMT offset
3966                 "if(zz != null){",
3967                     // reset to UTC, then add offset
3968                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3969                 "}else if(o){",
3970                     // reset to GMT, then add offset
3971                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3972                 "}",
3973             "}",
3974
3975             "return v;"
3976         ].join('\n');
3977
3978         return function(format) {
3979             var regexNum = utilDate.parseRegexes.length,
3980                 currentGroup = 1,
3981                 calc = [],
3982                 regex = [],
3983                 special = false,
3984                 ch = "";
3985
3986             for (var i = 0; i < format.length; ++i) {
3987                 ch = format.charAt(i);
3988                 if (!special && ch == "\\") {
3989                     special = true;
3990                 } else if (special) {
3991                     special = false;
3992                     regex.push(Ext.String.escape(ch));
3993                 } else {
3994                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3995                     currentGroup += obj.g;
3996                     regex.push(obj.s);
3997                     if (obj.g && obj.c) {
3998                         calc.push(obj.c);
3999                     }
4000                 }
4001             }
4002
4003             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
4004             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
4005         };
4006     })(),
4007
4008     // private
4009     parseCodes : {
4010         /*
4011          * Notes:
4012          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
4013          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
4014          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
4015          */
4016         d: {
4017             g:1,
4018             c:"d = parseInt(results[{0}], 10);\n",
4019             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
4020         },
4021         j: {
4022             g:1,
4023             c:"d = parseInt(results[{0}], 10);\n",
4024             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
4025         },
4026         D: function() {
4027             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
4028             return {
4029                 g:0,
4030                 c:null,
4031                 s:"(?:" + a.join("|") +")"
4032             };
4033         },
4034         l: function() {
4035             return {
4036                 g:0,
4037                 c:null,
4038                 s:"(?:" + utilDate.dayNames.join("|") + ")"
4039             };
4040         },
4041         N: {
4042             g:0,
4043             c:null,
4044             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4045         },
4046         S: {
4047             g:0,
4048             c:null,
4049             s:"(?:st|nd|rd|th)"
4050         },
4051         w: {
4052             g:0,
4053             c:null,
4054             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4055         },
4056         z: {
4057             g:1,
4058             c:"z = parseInt(results[{0}], 10);\n",
4059             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4060         },
4061         W: {
4062             g:0,
4063             c:null,
4064             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4065         },
4066         F: function() {
4067             return {
4068                 g:1,
4069                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4070                 s:"(" + utilDate.monthNames.join("|") + ")"
4071             };
4072         },
4073         M: function() {
4074             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4075             return Ext.applyIf({
4076                 s:"(" + a.join("|") + ")"
4077             }, utilDate.formatCodeToRegex("F"));
4078         },
4079         m: {
4080             g:1,
4081             c:"m = parseInt(results[{0}], 10) - 1;\n",
4082             s:"(\\d{2})" // month number with leading zeros (01 - 12)
4083         },
4084         n: {
4085             g:1,
4086             c:"m = parseInt(results[{0}], 10) - 1;\n",
4087             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4088         },
4089         t: {
4090             g:0,
4091             c:null,
4092             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4093         },
4094         L: {
4095             g:0,
4096             c:null,
4097             s:"(?:1|0)"
4098         },
4099         o: function() {
4100             return utilDate.formatCodeToRegex("Y");
4101         },
4102         Y: {
4103             g:1,
4104             c:"y = parseInt(results[{0}], 10);\n",
4105             s:"(\\d{4})" // 4-digit year
4106         },
4107         y: {
4108             g:1,
4109             c:"var ty = parseInt(results[{0}], 10);\n"
4110                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4111             s:"(\\d{1,2})"
4112         },
4113         /*
4114          * In the am/pm parsing routines, we allow both upper and lower case
4115          * even though it doesn't exactly match the spec. It gives much more flexibility
4116          * in being able to specify case insensitive regexes.
4117          */
4118         a: {
4119             g:1,
4120             c:"if (/(am)/i.test(results[{0}])) {\n"
4121                 + "if (!h || h == 12) { h = 0; }\n"
4122                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4123             s:"(am|pm|AM|PM)"
4124         },
4125         A: {
4126             g:1,
4127             c:"if (/(am)/i.test(results[{0}])) {\n"
4128                 + "if (!h || h == 12) { h = 0; }\n"
4129                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4130             s:"(AM|PM|am|pm)"
4131         },
4132         g: function() {
4133             return utilDate.formatCodeToRegex("G");
4134         },
4135         G: {
4136             g:1,
4137             c:"h = parseInt(results[{0}], 10);\n",
4138             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4139         },
4140         h: function() {
4141             return utilDate.formatCodeToRegex("H");
4142         },
4143         H: {
4144             g:1,
4145             c:"h = parseInt(results[{0}], 10);\n",
4146             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
4147         },
4148         i: {
4149             g:1,
4150             c:"i = parseInt(results[{0}], 10);\n",
4151             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4152         },
4153         s: {
4154             g:1,
4155             c:"s = parseInt(results[{0}], 10);\n",
4156             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4157         },
4158         u: {
4159             g:1,
4160             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4161             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4162         },
4163         O: {
4164             g:1,
4165             c:[
4166                 "o = results[{0}];",
4167                 "var sn = o.substring(0,1),", // get + / - sign
4168                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4169                     "mn = o.substring(3,5) % 60;", // get minutes
4170                 "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
4171             ].join("\n"),
4172             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4173         },
4174         P: {
4175             g:1,
4176             c:[
4177                 "o = results[{0}];",
4178                 "var sn = o.substring(0,1),", // get + / - sign
4179                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4180                     "mn = o.substring(4,6) % 60;", // get minutes
4181                 "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
4182             ].join("\n"),
4183             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4184         },
4185         T: {
4186             g:0,
4187             c:null,
4188             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4189         },
4190         Z: {
4191             g:1,
4192             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4193                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4194             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4195         },
4196         c: function() {
4197             var calc = [],
4198                 arr = [
4199                     utilDate.formatCodeToRegex("Y", 1), // year
4200                     utilDate.formatCodeToRegex("m", 2), // month
4201                     utilDate.formatCodeToRegex("d", 3), // day
4202                     utilDate.formatCodeToRegex("h", 4), // hour
4203                     utilDate.formatCodeToRegex("i", 5), // minute
4204                     utilDate.formatCodeToRegex("s", 6), // second
4205                     {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)
4206                     {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
4207                         "if(results[8]) {", // timezone specified
4208                             "if(results[8] == 'Z'){",
4209                                 "zz = 0;", // UTC
4210                             "}else if (results[8].indexOf(':') > -1){",
4211                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4212                             "}else{",
4213                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4214                             "}",
4215                         "}"
4216                     ].join('\n')}
4217                 ];
4218
4219             for (var i = 0, l = arr.length; i < l; ++i) {
4220                 calc.push(arr[i].c);
4221             }
4222
4223             return {
4224                 g:1,
4225                 c:calc.join(""),
4226                 s:[
4227                     arr[0].s, // year (required)
4228                     "(?:", "-", arr[1].s, // month (optional)
4229                         "(?:", "-", arr[2].s, // day (optional)
4230                             "(?:",
4231                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4232                                 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
4233                                 "(?::", arr[5].s, ")?", // seconds (optional)
4234                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4235                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4236                             ")?",
4237                         ")?",
4238                     ")?"
4239                 ].join("")
4240             };
4241         },
4242         U: {
4243             g:1,
4244             c:"u = parseInt(results[{0}], 10);\n",
4245             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4246         }
4247     },
4248
4249     //Old Ext.Date prototype methods.
4250     // private
4251     dateFormat: function(date, format) {
4252         return utilDate.format(date, format);
4253     },
4254
4255     /**
4256      * Formats a date given the supplied format string.
4257      * @param {Date} date The date to format
4258      * @param {String} format The format string
4259      * @return {String} The formatted date
4260      */
4261     format: function(date, format) {
4262         if (utilDate.formatFunctions[format] == null) {
4263             utilDate.createFormat(format);
4264         }
4265         var result = utilDate.formatFunctions[format].call(date);
4266         return result + '';
4267     },
4268
4269     /**
4270      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4271      *
4272      * Note: The date string returned by the javascript Date object's toString() method varies
4273      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4274      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4275      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4276      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4277      * from the GMT offset portion of the date string.
4278      * @param {Date} date The date
4279      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4280      */
4281     getTimezone : function(date) {
4282         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4283         //
4284         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4285         // 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)
4286         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4287         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4288         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4289         //
4290         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4291         // step 1: (?:\((.*)\) -- find timezone in parentheses
4292         // 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
4293         // step 3: remove all non uppercase characters found in step 1 and 2
4294         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4295     },
4296
4297     /**
4298      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4299      * @param {Date} date The date
4300      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4301      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4302      */
4303     getGMTOffset : function(date, colon) {
4304         var offset = date.getTimezoneOffset();
4305         return (offset > 0 ? "-" : "+")
4306             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4307             + (colon ? ":" : "")
4308             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4309     },
4310
4311     /**
4312      * Get the numeric day number of the year, adjusted for leap year.
4313      * @param {Date} date The date
4314      * @return {Number} 0 to 364 (365 in leap years).
4315      */
4316     getDayOfYear: function(date) {
4317         var num = 0,
4318             d = Ext.Date.clone(date),
4319             m = date.getMonth(),
4320             i;
4321
4322         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4323             num += utilDate.getDaysInMonth(d);
4324         }
4325         return num + date.getDate() - 1;
4326     },
4327
4328     /**
4329      * Get the numeric ISO-8601 week number of the year.
4330      * (equivalent to the format specifier 'W', but without a leading zero).
4331      * @param {Date} date The date
4332      * @return {Number} 1 to 53
4333      * @method
4334      */
4335     getWeekOfYear : (function() {
4336         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4337         var ms1d = 864e5, // milliseconds in a day
4338             ms7d = 7 * ms1d; // milliseconds in a week
4339
4340         return function(date) { // return a closure so constants get calculated only once
4341             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4342                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4343                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4344
4345             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4346         };
4347     })(),
4348
4349     /**
4350      * Checks if the current date falls within a leap year.
4351      * @param {Date} date The date
4352      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4353      */
4354     isLeapYear : function(date) {
4355         var year = date.getFullYear();
4356         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4357     },
4358
4359     /**
4360      * Get the first day of the current month, adjusted for leap year.  The returned value
4361      * is the numeric day index within the week (0-6) which can be used in conjunction with
4362      * the {@link #monthNames} array to retrieve the textual day name.
4363      * Example:
4364      * <pre><code>
4365 var dt = new Date('1/10/2007'),
4366     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4367 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4368      * </code></pre>
4369      * @param {Date} date The date
4370      * @return {Number} The day number (0-6).
4371      */
4372     getFirstDayOfMonth : function(date) {
4373         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4374         return (day < 0) ? (day + 7) : day;
4375     },
4376
4377     /**
4378      * Get the last day of the current month, adjusted for leap year.  The returned value
4379      * is the numeric day index within the week (0-6) which can be used in conjunction with
4380      * the {@link #monthNames} array to retrieve the textual day name.
4381      * Example:
4382      * <pre><code>
4383 var dt = new Date('1/10/2007'),
4384     lastDay = Ext.Date.getLastDayOfMonth(dt);
4385 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4386      * </code></pre>
4387      * @param {Date} date The date
4388      * @return {Number} The day number (0-6).
4389      */
4390     getLastDayOfMonth : function(date) {
4391         return utilDate.getLastDateOfMonth(date).getDay();
4392     },
4393
4394
4395     /**
4396      * Get the date of the first day of the month in which this date resides.
4397      * @param {Date} date The date
4398      * @return {Date}
4399      */
4400     getFirstDateOfMonth : function(date) {
4401         return new Date(date.getFullYear(), date.getMonth(), 1);
4402     },
4403
4404     /**
4405      * Get the date of the last day of the month in which this date resides.
4406      * @param {Date} date The date
4407      * @return {Date}
4408      */
4409     getLastDateOfMonth : function(date) {
4410         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4411     },
4412
4413     /**
4414      * Get the number of days in the current month, adjusted for leap year.
4415      * @param {Date} date The date
4416      * @return {Number} The number of days in the month.
4417      * @method
4418      */
4419     getDaysInMonth: (function() {
4420         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4421
4422         return function(date) { // return a closure for efficiency
4423             var m = date.getMonth();
4424
4425             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4426         };
4427     })(),
4428
4429     /**
4430      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4431      * @param {Date} date The date
4432      * @return {String} 'st, 'nd', 'rd' or 'th'.
4433      */
4434     getSuffix : function(date) {
4435         switch (date.getDate()) {
4436             case 1:
4437             case 21:
4438             case 31:
4439                 return "st";
4440             case 2:
4441             case 22:
4442                 return "nd";
4443             case 3:
4444             case 23:
4445                 return "rd";
4446             default:
4447                 return "th";
4448         }
4449     },
4450
4451     /**
4452      * Creates and returns a new Date instance with the exact same date value as the called instance.
4453      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4454      * variable will also be changed.  When the intention is to create a new variable that will not
4455      * modify the original instance, you should create a clone.
4456      *
4457      * Example of correctly cloning a date:
4458      * <pre><code>
4459 //wrong way:
4460 var orig = new Date('10/1/2006');
4461 var copy = orig;
4462 copy.setDate(5);
4463 console.log(orig);  //returns 'Thu Oct 05 2006'!
4464
4465 //correct way:
4466 var orig = new Date('10/1/2006'),
4467     copy = Ext.Date.clone(orig);
4468 copy.setDate(5);
4469 console.log(orig);  //returns 'Thu Oct 01 2006'
4470      * </code></pre>
4471      * @param {Date} date The date
4472      * @return {Date} The new Date instance.
4473      */
4474     clone : function(date) {
4475         return new Date(date.getTime());
4476     },
4477
4478     /**
4479      * Checks if the current date is affected by Daylight Saving Time (DST).
4480      * @param {Date} date The date
4481      * @return {Boolean} True if the current date is affected by DST.
4482      */
4483     isDST : function(date) {
4484         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4485         // courtesy of @geoffrey.mcgill
4486         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4487     },
4488
4489     /**
4490      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4491      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4492      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4493      * @param {Date} date The date
4494      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4495      * @return {Date} this or the clone.
4496      */
4497     clearTime : function(date, clone) {
4498         if (clone) {
4499             return Ext.Date.clearTime(Ext.Date.clone(date));
4500         }
4501
4502         // get current date before clearing time
4503         var d = date.getDate();
4504
4505         // clear time
4506         date.setHours(0);
4507         date.setMinutes(0);
4508         date.setSeconds(0);
4509         date.setMilliseconds(0);
4510
4511         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4512             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4513             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4514
4515             // increment hour until cloned date == current date
4516             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4517
4518             date.setDate(d);
4519             date.setHours(c.getHours());
4520         }
4521
4522         return date;
4523     },
4524
4525     /**
4526      * Provides a convenient method for performing basic date arithmetic. This method
4527      * does not modify the Date instance being called - it creates and returns
4528      * a new Date instance containing the resulting date value.
4529      *
4530      * Examples:
4531      * <pre><code>
4532 // Basic usage:
4533 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4534 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4535
4536 // Negative values will be subtracted:
4537 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4538 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4539
4540      * </code></pre>
4541      *
4542      * @param {Date} date The date to modify
4543      * @param {String} interval A valid date interval enum value.
4544      * @param {Number} value The amount to add to the current date.
4545      * @return {Date} The new Date instance.
4546      */
4547     add : function(date, interval, value) {
4548         var d = Ext.Date.clone(date),
4549             Date = Ext.Date;
4550         if (!interval || value === 0) return d;
4551
4552         switch(interval.toLowerCase()) {
4553             case Ext.Date.MILLI:
4554                 d.setMilliseconds(d.getMilliseconds() + value);
4555                 break;
4556             case Ext.Date.SECOND:
4557                 d.setSeconds(d.getSeconds() + value);
4558                 break;
4559             case Ext.Date.MINUTE:
4560                 d.setMinutes(d.getMinutes() + value);
4561                 break;
4562             case Ext.Date.HOUR:
4563                 d.setHours(d.getHours() + value);
4564                 break;
4565             case Ext.Date.DAY:
4566                 d.setDate(d.getDate() + value);
4567                 break;
4568             case Ext.Date.MONTH:
4569                 var day = date.getDate();
4570                 if (day > 28) {
4571                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4572                 }
4573                 d.setDate(day);
4574                 d.setMonth(date.getMonth() + value);
4575                 break;
4576             case Ext.Date.YEAR:
4577                 d.setFullYear(date.getFullYear() + value);
4578                 break;
4579         }
4580         return d;
4581     },
4582
4583     /**
4584      * Checks if a date falls on or between the given start and end dates.
4585      * @param {Date} date The date to check
4586      * @param {Date} start Start date
4587      * @param {Date} end End date
4588      * @return {Boolean} true if this date falls on or between the given start and end dates.
4589      */
4590     between : function(date, start, end) {
4591         var t = date.getTime();
4592         return start.getTime() <= t && t <= end.getTime();
4593     },
4594
4595     //Maintains compatibility with old static and prototype window.Date methods.
4596     compat: function() {
4597         var nativeDate = window.Date,
4598             p, u,
4599             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'],
4600             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4601
4602         //Append statics
4603         Ext.Array.forEach(statics, function(s) {
4604             nativeDate[s] = utilDate[s];
4605         });
4606
4607         //Append to prototype
4608         Ext.Array.forEach(proto, function(s) {
4609             nativeDate.prototype[s] = function() {
4610                 var args = Array.prototype.slice.call(arguments);
4611                 args.unshift(this);
4612                 return utilDate[s].apply(utilDate, args);
4613             };
4614         });
4615     }
4616 };
4617
4618 var utilDate = Ext.Date;
4619
4620 })();
4621
4622 /**
4623  * @author Jacky Nguyen <jacky@sencha.com>
4624  * @docauthor Jacky Nguyen <jacky@sencha.com>
4625  * @class Ext.Base
4626  *
4627  * The root of all classes created with {@link Ext#define}.
4628  *
4629  * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
4630  * All prototype and static members of this class are inherited by all other classes.
4631  */
4632 (function(flexSetter) {
4633
4634 var Base = Ext.Base = function() {};
4635     Base.prototype = {
4636         $className: 'Ext.Base',
4637
4638         $class: Base,
4639
4640         /**
4641          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4642          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4643          * for a detailed comparison
4644          *
4645          *     Ext.define('My.Cat', {
4646          *         statics: {
4647          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4648          *         },
4649          *
4650          *         constructor: function() {
4651          *             alert(this.self.speciesName); / dependent on 'this'
4652          *
4653          *             return this;
4654          *         },
4655          *
4656          *         clone: function() {
4657          *             return new this.self();
4658          *         }
4659          *     });
4660          *
4661          *
4662          *     Ext.define('My.SnowLeopard', {
4663          *         extend: 'My.Cat',
4664          *         statics: {
4665          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4666          *         }
4667          *     });
4668          *
4669          *     var cat = new My.Cat();                     // alerts 'Cat'
4670          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4671          *
4672          *     var clone = snowLeopard.clone();
4673          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4674          *
4675          * @type Ext.Class
4676          * @protected
4677          */
4678         self: Base,
4679
4680         // Default constructor, simply returns `this`
4681         constructor: function() {
4682             return this;
4683         },
4684
4685         //<feature classSystem.config>
4686         /**
4687          * Initialize configuration for this class. a typical example:
4688          *
4689          *     Ext.define('My.awesome.Class', {
4690          *         // The default config
4691          *         config: {
4692          *             name: 'Awesome',
4693          *             isAwesome: true
4694          *         },
4695          *
4696          *         constructor: function(config) {
4697          *             this.initConfig(config);
4698          *
4699          *             return this;
4700          *         }
4701          *     });
4702          *
4703          *     var awesome = new My.awesome.Class({
4704          *         name: 'Super Awesome'
4705          *     });
4706          *
4707          *     alert(awesome.getName()); // 'Super Awesome'
4708          *
4709          * @protected
4710          * @param {Object} config
4711          * @return {Object} mixins The mixin prototypes as key - value pairs
4712          */
4713         initConfig: function(config) {
4714             if (!this.$configInited) {
4715                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4716
4717                 this.applyConfig(this.config);
4718
4719                 this.$configInited = true;
4720             }
4721
4722             return this;
4723         },
4724
4725         /**
4726          * @private
4727          */
4728         setConfig: function(config) {
4729             this.applyConfig(config || {});
4730
4731             return this;
4732         },
4733
4734         /**
4735          * @private
4736          */
4737         applyConfig: flexSetter(function(name, value) {
4738             var setter = 'set' + Ext.String.capitalize(name);
4739
4740             if (typeof this[setter] === 'function') {
4741                 this[setter].call(this, value);
4742             }
4743
4744             return this;
4745         }),
4746         //</feature>
4747
4748         /**
4749          * Call the parent's overridden method. For example:
4750          *
4751          *     Ext.define('My.own.A', {
4752          *         constructor: function(test) {
4753          *             alert(test);
4754          *         }
4755          *     });
4756          *
4757          *     Ext.define('My.own.B', {
4758          *         extend: 'My.own.A',
4759          *
4760          *         constructor: function(test) {
4761          *             alert(test);
4762          *
4763          *             this.callParent([test + 1]);
4764          *         }
4765          *     });
4766          *
4767          *     Ext.define('My.own.C', {
4768          *         extend: 'My.own.B',
4769          *
4770          *         constructor: function() {
4771          *             alert("Going to call parent's overriden constructor...");
4772          *
4773          *             this.callParent(arguments);
4774          *         }
4775          *     });
4776          *
4777          *     var a = new My.own.A(1); // alerts '1'
4778          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
4779          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4780          *                              // alerts '2', then alerts '3'
4781          *
4782          * @protected
4783          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4784          * from the current method, for example: `this.callParent(arguments)`
4785          * @return {Object} Returns the result from the superclass' method
4786          */
4787         callParent: function(args) {
4788             var method = this.callParent.caller,
4789                 parentClass, methodName;
4790
4791             if (!method.$owner) {
4792
4793                 method = method.caller;
4794             }
4795
4796             parentClass = method.$owner.superclass;
4797             methodName = method.$name;
4798
4799
4800             return parentClass[methodName].apply(this, args || []);
4801         },
4802
4803
4804         /**
4805          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4806          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4807          * `this` points to during run-time
4808          *
4809          *     Ext.define('My.Cat', {
4810          *         statics: {
4811          *             totalCreated: 0,
4812          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4813          *         },
4814          *
4815          *         constructor: function() {
4816          *             var statics = this.statics();
4817          *
4818          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4819          *                                             // equivalent to: My.Cat.speciesName
4820          *
4821          *             alert(this.self.speciesName);   // dependent on 'this'
4822          *
4823          *             statics.totalCreated++;
4824          *
4825          *             return this;
4826          *         },
4827          *
4828          *         clone: function() {
4829          *             var cloned = new this.self;                      // dependent on 'this'
4830          *
4831          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4832          *
4833          *             return cloned;
4834          *         }
4835          *     });
4836          *
4837          *
4838          *     Ext.define('My.SnowLeopard', {
4839          *         extend: 'My.Cat',
4840          *
4841          *         statics: {
4842          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4843          *         },
4844          *
4845          *         constructor: function() {
4846          *             this.callParent();
4847          *         }
4848          *     });
4849          *
4850          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4851          *
4852          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4853          *
4854          *     var clone = snowLeopard.clone();
4855          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4856          *     alert(clone.groupName);                 // alerts 'Cat'
4857          *
4858          *     alert(My.Cat.totalCreated);             // alerts 3
4859          *
4860          * @protected
4861          * @return {Ext.Class}
4862          */
4863         statics: function() {
4864             var method = this.statics.caller,
4865                 self = this.self;
4866
4867             if (!method) {
4868                 return self;
4869             }
4870
4871             return method.$owner;
4872         },
4873
4874         /**
4875          * Call the original method that was previously overridden with {@link Ext.Base#override}
4876          *
4877          *     Ext.define('My.Cat', {
4878          *         constructor: function() {
4879          *             alert("I'm a cat!");
4880          *
4881          *             return this;
4882          *         }
4883          *     });
4884          *
4885          *     My.Cat.override({
4886          *         constructor: function() {
4887          *             alert("I'm going to be a cat!");
4888          *
4889          *             var instance = this.callOverridden();
4890          *
4891          *             alert("Meeeeoooowwww");
4892          *
4893          *             return instance;
4894          *         }
4895          *     });
4896          *
4897          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4898          *                               // alerts "I'm a cat!"
4899          *                               // alerts "Meeeeoooowwww"
4900          *
4901          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4902          * @return {Object} Returns the result after calling the overridden method
4903          * @protected
4904          */
4905         callOverridden: function(args) {
4906             var method = this.callOverridden.caller;
4907
4908
4909             return method.$previous.apply(this, args || []);
4910         },
4911
4912         destroy: function() {}
4913     };
4914
4915     // These static properties will be copied to every newly created class with {@link Ext#define}
4916     Ext.apply(Ext.Base, {
4917         /**
4918          * Create a new instance of this Class.
4919          *
4920          *     Ext.define('My.cool.Class', {
4921          *         ...
4922          *     });
4923          *
4924          *     My.cool.Class.create({
4925          *         someConfig: true
4926          *     });
4927          *
4928          * All parameters are passed to the constructor of the class.
4929          *
4930          * @return {Object} the created instance.
4931          * @static
4932          * @inheritable
4933          */
4934         create: function() {
4935             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4936         },
4937
4938         /**
4939          * @private
4940          * @inheritable
4941          */
4942         own: function(name, value) {
4943             if (typeof value == 'function') {
4944                 this.ownMethod(name, value);
4945             }
4946             else {
4947                 this.prototype[name] = value;
4948             }
4949         },
4950
4951         /**
4952          * @private
4953          * @inheritable
4954          */
4955         ownMethod: function(name, fn) {
4956             var originalFn;
4957
4958             if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
4959                 originalFn = fn;
4960
4961                 fn = function() {
4962                     return originalFn.apply(this, arguments);
4963                 };
4964             }
4965
4966             fn.$owner = this;
4967             fn.$name = name;
4968
4969             this.prototype[name] = fn;
4970         },
4971
4972         /**
4973          * Add / override static properties of this class.
4974          *
4975          *     Ext.define('My.cool.Class', {
4976          *         ...
4977          *     });
4978          *
4979          *     My.cool.Class.addStatics({
4980          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
4981          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
4982          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
4983          *     });
4984          *
4985          * @param {Object} members
4986          * @return {Ext.Base} this
4987          * @static
4988          * @inheritable
4989          */
4990         addStatics: function(members) {
4991             for (var name in members) {
4992                 if (members.hasOwnProperty(name)) {
4993                     this[name] = members[name];
4994                 }
4995             }
4996
4997             return this;
4998         },
4999
5000         /**
5001          * @private
5002          * @param {Object} members
5003          */
5004         addInheritableStatics: function(members) {
5005             var inheritableStatics,
5006                 hasInheritableStatics,
5007                 prototype = this.prototype,
5008                 name, member;
5009
5010             inheritableStatics = prototype.$inheritableStatics;
5011             hasInheritableStatics = prototype.$hasInheritableStatics;
5012
5013             if (!inheritableStatics) {
5014                 inheritableStatics = prototype.$inheritableStatics = [];
5015                 hasInheritableStatics = prototype.$hasInheritableStatics = {};
5016             }
5017
5018
5019             for (name in members) {
5020                 if (members.hasOwnProperty(name)) {
5021                     member = members[name];
5022                     this[name] = member;
5023
5024                     if (!hasInheritableStatics[name]) {
5025                         hasInheritableStatics[name] = true;
5026                         inheritableStatics.push(name);
5027                     }
5028                 }
5029             }
5030
5031             return this;
5032         },
5033
5034         /**
5035          * Add methods / properties to the prototype of this class.
5036          *
5037          *     Ext.define('My.awesome.Cat', {
5038          *         constructor: function() {
5039          *             ...
5040          *         }
5041          *     });
5042          *
5043          *      My.awesome.Cat.implement({
5044          *          meow: function() {
5045          *             alert('Meowww...');
5046          *          }
5047          *      });
5048          *
5049          *      var kitty = new My.awesome.Cat;
5050          *      kitty.meow();
5051          *
5052          * @param {Object} members
5053          * @static
5054          * @inheritable
5055          */
5056         implement: function(members) {
5057             var prototype = this.prototype,
5058                 enumerables = Ext.enumerables,
5059                 name, i, member;
5060             for (name in members) {
5061                 if (members.hasOwnProperty(name)) {
5062                     member = members[name];
5063
5064                     if (typeof member === 'function') {
5065                         member.$owner = this;
5066                         member.$name = name;
5067                     }
5068
5069                     prototype[name] = member;
5070                 }
5071             }
5072
5073             if (enumerables) {
5074                 for (i = enumerables.length; i--;) {
5075                     name = enumerables[i];
5076
5077                     if (members.hasOwnProperty(name)) {
5078                         member = members[name];
5079                         member.$owner = this;
5080                         member.$name = name;
5081                         prototype[name] = member;
5082                     }
5083                 }
5084             }
5085         },
5086
5087         /**
5088          * Borrow another class' members to the prototype of this class.
5089          *
5090          *     Ext.define('Bank', {
5091          *         money: '$$$',
5092          *         printMoney: function() {
5093          *             alert('$$$$$$$');
5094          *         }
5095          *     });
5096          *
5097          *     Ext.define('Thief', {
5098          *         ...
5099          *     });
5100          *
5101          *     Thief.borrow(Bank, ['money', 'printMoney']);
5102          *
5103          *     var steve = new Thief();
5104          *
5105          *     alert(steve.money); // alerts '$$$'
5106          *     steve.printMoney(); // alerts '$$$$$$$'
5107          *
5108          * @param {Ext.Base} fromClass The class to borrow members from
5109          * @param {String/String[]} members The names of the members to borrow
5110          * @return {Ext.Base} this
5111          * @static
5112          * @inheritable
5113          */
5114         borrow: function(fromClass, members) {
5115             var fromPrototype = fromClass.prototype,
5116                 i, ln, member;
5117
5118             members = Ext.Array.from(members);
5119
5120             for (i = 0, ln = members.length; i < ln; i++) {
5121                 member = members[i];
5122
5123                 this.own(member, fromPrototype[member]);
5124             }
5125
5126             return this;
5127         },
5128
5129         /**
5130          * Override prototype members of this class. Overridden methods can be invoked via
5131          * {@link Ext.Base#callOverridden}
5132          *
5133          *     Ext.define('My.Cat', {
5134          *         constructor: function() {
5135          *             alert("I'm a cat!");
5136          *
5137          *             return this;
5138          *         }
5139          *     });
5140          *
5141          *     My.Cat.override({
5142          *         constructor: function() {
5143          *             alert("I'm going to be a cat!");
5144          *
5145          *             var instance = this.callOverridden();
5146          *
5147          *             alert("Meeeeoooowwww");
5148          *
5149          *             return instance;
5150          *         }
5151          *     });
5152          *
5153          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5154          *                               // alerts "I'm a cat!"
5155          *                               // alerts "Meeeeoooowwww"
5156          *
5157          * @param {Object} members
5158          * @return {Ext.Base} this
5159          * @static
5160          * @inheritable
5161          */
5162         override: function(members) {
5163             var prototype = this.prototype,
5164                 enumerables = Ext.enumerables,
5165                 name, i, member, previous;
5166
5167             if (arguments.length === 2) {
5168                 name = members;
5169                 member = arguments[1];
5170
5171                 if (typeof member == 'function') {
5172                     if (typeof prototype[name] == 'function') {
5173                         previous = prototype[name];
5174                         member.$previous = previous;
5175                     }
5176
5177                     this.ownMethod(name, member);
5178                 }
5179                 else {
5180                     prototype[name] = member;
5181                 }
5182
5183                 return this;
5184             }
5185
5186             for (name in members) {
5187                 if (members.hasOwnProperty(name)) {
5188                     member = members[name];
5189
5190                     if (typeof member === 'function') {
5191                         if (typeof prototype[name] === 'function') {
5192                             previous = prototype[name];
5193                             member.$previous = previous;
5194                         }
5195
5196                         this.ownMethod(name, member);
5197                     }
5198                     else {
5199                         prototype[name] = member;
5200                     }
5201                 }
5202             }
5203
5204             if (enumerables) {
5205                 for (i = enumerables.length; i--;) {
5206                     name = enumerables[i];
5207
5208                     if (members.hasOwnProperty(name)) {
5209                         if (typeof prototype[name] !== 'undefined') {
5210                             previous = prototype[name];
5211                             members[name].$previous = previous;
5212                         }
5213
5214                         this.ownMethod(name, members[name]);
5215                     }
5216                 }
5217             }
5218
5219             return this;
5220         },
5221
5222         //<feature classSystem.mixins>
5223         /**
5224          * Used internally by the mixins pre-processor
5225          * @private
5226          * @inheritable
5227          */
5228         mixin: function(name, cls) {
5229             var mixin = cls.prototype,
5230                 my = this.prototype,
5231                 key, fn;
5232
5233             for (key in mixin) {
5234                 if (mixin.hasOwnProperty(key)) {
5235                     if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
5236                         if (typeof mixin[key] === 'function') {
5237                             fn = mixin[key];
5238
5239                             if (typeof fn.$owner === 'undefined') {
5240                                 this.ownMethod(key, fn);
5241                             }
5242                             else {
5243                                 my[key] = fn;
5244                             }
5245                         }
5246                         else {
5247                             my[key] = mixin[key];
5248                         }
5249                     }
5250                     //<feature classSystem.config>
5251                     else if (key === 'config' && my.config && mixin.config) {
5252                         Ext.Object.merge(my.config, mixin.config);
5253                     }
5254                     //</feature>
5255                 }
5256             }
5257
5258             if (typeof mixin.onClassMixedIn !== 'undefined') {
5259                 mixin.onClassMixedIn.call(cls, this);
5260             }
5261
5262             if (!my.hasOwnProperty('mixins')) {
5263                 if ('mixins' in my) {
5264                     my.mixins = Ext.Object.merge({}, my.mixins);
5265                 }
5266                 else {
5267                     my.mixins = {};
5268                 }
5269             }
5270
5271             my.mixins[name] = mixin;
5272         },
5273         //</feature>
5274
5275         /**
5276          * Get the current class' name in string format.
5277          *
5278          *     Ext.define('My.cool.Class', {
5279          *         constructor: function() {
5280          *             alert(this.self.getName()); // alerts 'My.cool.Class'
5281          *         }
5282          *     });
5283          *
5284          *     My.cool.Class.getName(); // 'My.cool.Class'
5285          *
5286          * @return {String} className
5287          * @static
5288          * @inheritable
5289          */
5290         getName: function() {
5291             return Ext.getClassName(this);
5292         },
5293
5294         /**
5295          * Create aliases for existing prototype methods. Example:
5296          *
5297          *     Ext.define('My.cool.Class', {
5298          *         method1: function() { ... },
5299          *         method2: function() { ... }
5300          *     });
5301          *
5302          *     var test = new My.cool.Class();
5303          *
5304          *     My.cool.Class.createAlias({
5305          *         method3: 'method1',
5306          *         method4: 'method2'
5307          *     });
5308          *
5309          *     test.method3(); // test.method1()
5310          *
5311          *     My.cool.Class.createAlias('method5', 'method3');
5312          *
5313          *     test.method5(); // test.method3() -> test.method1()
5314          *
5315          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5316          * {@link Ext.Function#flexSetter flexSetter}
5317          * @param {String/Object} origin The original method name
5318          * @static
5319          * @inheritable
5320          * @method
5321          */
5322         createAlias: flexSetter(function(alias, origin) {
5323             this.prototype[alias] = function() {
5324                 return this[origin].apply(this, arguments);
5325             }
5326         })
5327     });
5328
5329 })(Ext.Function.flexSetter);
5330
5331 /**
5332  * @author Jacky Nguyen <jacky@sencha.com>
5333  * @docauthor Jacky Nguyen <jacky@sencha.com>
5334  * @class Ext.Class
5335  *
5336  * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
5337  * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
5338  * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
5339  *
5340  * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
5341  * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
5342  *
5343  * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
5344  * from, see {@link Ext.Base}.
5345  */
5346 (function() {
5347
5348     var Class,
5349         Base = Ext.Base,
5350         baseStaticProperties = [],
5351         baseStaticProperty;
5352
5353     for (baseStaticProperty in Base) {
5354         if (Base.hasOwnProperty(baseStaticProperty)) {
5355             baseStaticProperties.push(baseStaticProperty);
5356         }
5357     }
5358
5359     /**
5360      * @method constructor
5361      * Creates new class.
5362      * @param {Object} classData An object represent the properties of this class
5363      * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
5364      * Note that the creation process can be asynchronous depending on the pre-processors used.
5365      * @return {Ext.Base} The newly created class
5366      */
5367     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5368         if (typeof newClass != 'function') {
5369             onClassCreated = classData;
5370             classData = newClass;
5371             newClass = function() {
5372                 return this.constructor.apply(this, arguments);
5373             };
5374         }
5375
5376         if (!classData) {
5377             classData = {};
5378         }
5379
5380         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5381             registeredPreprocessors = Class.getPreprocessors(),
5382             index = 0,
5383             preprocessors = [],
5384             preprocessor, staticPropertyName, process, i, j, ln;
5385
5386         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5387             staticPropertyName = baseStaticProperties[i];
5388             newClass[staticPropertyName] = Base[staticPropertyName];
5389         }
5390
5391         delete classData.preprocessors;
5392
5393         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5394             preprocessor = preprocessorStack[j];
5395
5396             if (typeof preprocessor == 'string') {
5397                 preprocessor = registeredPreprocessors[preprocessor];
5398
5399                 if (!preprocessor.always) {
5400                     if (classData.hasOwnProperty(preprocessor.name)) {
5401                         preprocessors.push(preprocessor.fn);
5402                     }
5403                 }
5404                 else {
5405                     preprocessors.push(preprocessor.fn);
5406                 }
5407             }
5408             else {
5409                 preprocessors.push(preprocessor);
5410             }
5411         }
5412
5413         classData.onClassCreated = onClassCreated || Ext.emptyFn;
5414
5415         classData.onBeforeClassCreated = function(cls, data) {
5416             onClassCreated = data.onClassCreated;
5417
5418             delete data.onBeforeClassCreated;
5419             delete data.onClassCreated;
5420
5421             cls.implement(data);
5422
5423             onClassCreated.call(cls, cls);
5424         };
5425
5426         process = function(cls, data) {
5427             preprocessor = preprocessors[index++];
5428
5429             if (!preprocessor) {
5430                 data.onBeforeClassCreated.apply(this, arguments);
5431                 return;
5432             }
5433
5434             if (preprocessor.call(this, cls, data, process) !== false) {
5435                 process.apply(this, arguments);
5436             }
5437         };
5438
5439         process.call(Class, newClass, classData);
5440
5441         return newClass;
5442     };
5443
5444     Ext.apply(Class, {
5445
5446         /** @private */
5447         preprocessors: {},
5448
5449         /**
5450          * Register a new pre-processor to be used during the class creation process
5451          *
5452          * @member Ext.Class
5453          * @param {String} name The pre-processor's name
5454          * @param {Function} fn The callback function to be executed. Typical format:
5455          *
5456          *     function(cls, data, fn) {
5457          *         // Your code here
5458          *
5459          *         // Execute this when the processing is finished.
5460          *         // Asynchronous processing is perfectly ok
5461          *         if (fn) {
5462          *             fn.call(this, cls, data);
5463          *         }
5464          *     });
5465          *
5466          * @param {Function} fn.cls The created class
5467          * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
5468          * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
5469          * regardless of whether the processing is synchronous or aynchronous
5470          *
5471          * @return {Ext.Class} this
5472          * @static
5473          */
5474         registerPreprocessor: function(name, fn, always) {
5475             this.preprocessors[name] = {
5476                 name: name,
5477                 always: always ||  false,
5478                 fn: fn
5479             };
5480
5481             return this;
5482         },
5483
5484         /**
5485          * Retrieve a pre-processor callback function by its name, which has been registered before
5486          *
5487          * @param {String} name
5488          * @return {Function} preprocessor
5489          * @static
5490          */
5491         getPreprocessor: function(name) {
5492             return this.preprocessors[name];
5493         },
5494
5495         getPreprocessors: function() {
5496             return this.preprocessors;
5497         },
5498
5499         /**
5500          * Retrieve the array stack of default pre-processors
5501          *
5502          * @return {Function[]} defaultPreprocessors
5503          * @static
5504          */
5505         getDefaultPreprocessors: function() {
5506             return this.defaultPreprocessors || [];
5507         },
5508
5509         /**
5510          * Set the default array stack of default pre-processors
5511          *
5512          * @param {Function/Function[]} preprocessors
5513          * @return {Ext.Class} this
5514          * @static
5515          */
5516         setDefaultPreprocessors: function(preprocessors) {
5517             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5518
5519             return this;
5520         },
5521
5522         /**
5523          * Inserts this pre-processor at a specific position in the stack, optionally relative to
5524          * any existing pre-processor. For example:
5525          *
5526          *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5527          *         // Your code here
5528          *
5529          *         if (fn) {
5530          *             fn.call(this, cls, data);
5531          *         }
5532          *     }).setDefaultPreprocessorPosition('debug', 'last');
5533          *
5534          * @param {String} name The pre-processor name. Note that it needs to be registered with
5535          * {@link #registerPreprocessor registerPreprocessor} before this
5536          * @param {String} offset The insertion position. Four possible values are:
5537          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5538          * @param {String} relativeName
5539          * @return {Ext.Class} this
5540          * @static
5541          */
5542         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5543             var defaultPreprocessors = this.defaultPreprocessors,
5544                 index;
5545
5546             if (typeof offset == 'string') {
5547                 if (offset === 'first') {
5548                     defaultPreprocessors.unshift(name);
5549
5550                     return this;
5551                 }
5552                 else if (offset === 'last') {
5553                     defaultPreprocessors.push(name);
5554
5555                     return this;
5556                 }
5557
5558                 offset = (offset === 'after') ? 1 : -1;
5559             }
5560
5561             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5562
5563             if (index !== -1) {
5564                 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5565             }
5566
5567             return this;
5568         }
5569     });
5570
5571     /**
5572      * @cfg {String} extend
5573      * The parent class that this class extends. For example:
5574      *
5575      *     Ext.define('Person', {
5576      *         say: function(text) { alert(text); }
5577      *     });
5578      *
5579      *     Ext.define('Developer', {
5580      *         extend: 'Person',
5581      *         say: function(text) { this.callParent(["print "+text]); }
5582      *     });
5583      */
5584     Class.registerPreprocessor('extend', function(cls, data) {
5585         var extend = data.extend,
5586             base = Ext.Base,
5587             basePrototype = base.prototype,
5588             prototype = function() {},
5589             parent, i, k, ln, staticName, parentStatics,
5590             parentPrototype, clsPrototype;
5591
5592         if (extend && extend !== Object) {
5593             parent = extend;
5594         }
5595         else {
5596             parent = base;
5597         }
5598
5599         parentPrototype = parent.prototype;
5600
5601         prototype.prototype = parentPrototype;
5602         clsPrototype = cls.prototype = new prototype();
5603
5604         if (!('$class' in parent)) {
5605             for (i in basePrototype) {
5606                 if (!parentPrototype[i]) {
5607                     parentPrototype[i] = basePrototype[i];
5608                 }
5609             }
5610         }
5611
5612         clsPrototype.self = cls;
5613
5614         cls.superclass = clsPrototype.superclass = parentPrototype;
5615
5616         delete data.extend;
5617
5618         //<feature classSystem.inheritableStatics>
5619         // Statics inheritance
5620         parentStatics = parentPrototype.$inheritableStatics;
5621
5622         if (parentStatics) {
5623             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5624                 staticName = parentStatics[k];
5625
5626                 if (!cls.hasOwnProperty(staticName)) {
5627                     cls[staticName] = parent[staticName];
5628                 }
5629             }
5630         }
5631         //</feature>
5632
5633         //<feature classSystem.config>
5634         // Merge the parent class' config object without referencing it
5635         if (parentPrototype.config) {
5636             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5637         }
5638         else {
5639             clsPrototype.config = {};
5640         }
5641         //</feature>
5642
5643         //<feature classSystem.onClassExtended>
5644         if (clsPrototype.$onExtended) {
5645             clsPrototype.$onExtended.call(cls, cls, data);
5646         }
5647
5648         if (data.onClassExtended) {
5649             clsPrototype.$onExtended = data.onClassExtended;
5650             delete data.onClassExtended;
5651         }
5652         //</feature>
5653
5654     }, true);
5655
5656     //<feature classSystem.statics>
5657     /**
5658      * @cfg {Object} statics
5659      * List of static methods for this class. For example:
5660      *
5661      *     Ext.define('Computer', {
5662      *          statics: {
5663      *              factory: function(brand) {
5664      *                  // 'this' in static methods refer to the class itself
5665      *                  return new this(brand);
5666      *              }
5667      *          },
5668      *
5669      *          constructor: function() { ... }
5670      *     });
5671      *
5672      *     var dellComputer = Computer.factory('Dell');
5673      */
5674     Class.registerPreprocessor('statics', function(cls, data) {
5675         cls.addStatics(data.statics);
5676
5677         delete data.statics;
5678     });
5679     //</feature>
5680
5681     //<feature classSystem.inheritableStatics>
5682     /**
5683      * @cfg {Object} inheritableStatics
5684      * List of inheritable static methods for this class.
5685      * Otherwise just like {@link #statics} but subclasses inherit these methods.
5686      */
5687     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5688         cls.addInheritableStatics(data.inheritableStatics);
5689
5690         delete data.inheritableStatics;
5691     });
5692     //</feature>
5693
5694     //<feature classSystem.config>
5695     /**
5696      * @cfg {Object} config
5697      * List of configuration options with their default values, for which automatically
5698      * accessor methods are generated.  For example:
5699      *
5700      *     Ext.define('SmartPhone', {
5701      *          config: {
5702      *              hasTouchScreen: false,
5703      *              operatingSystem: 'Other',
5704      *              price: 500
5705      *          },
5706      *          constructor: function(cfg) {
5707      *              this.initConfig(cfg);
5708      *          }
5709      *     });
5710      *
5711      *     var iPhone = new SmartPhone({
5712      *          hasTouchScreen: true,
5713      *          operatingSystem: 'iOS'
5714      *     });
5715      *
5716      *     iPhone.getPrice(); // 500;
5717      *     iPhone.getOperatingSystem(); // 'iOS'
5718      *     iPhone.getHasTouchScreen(); // true;
5719      *     iPhone.hasTouchScreen(); // true
5720      */
5721     Class.registerPreprocessor('config', function(cls, data) {
5722         var prototype = cls.prototype;
5723
5724         Ext.Object.each(data.config, function(name) {
5725             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5726                 pName = name,
5727                 apply = 'apply' + cName,
5728                 setter = 'set' + cName,
5729                 getter = 'get' + cName;
5730
5731             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5732                 data[apply] = function(val) {
5733                     return val;
5734                 };
5735             }
5736
5737             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5738                 data[setter] = function(val) {
5739                     var ret = this[apply].call(this, val, this[pName]);
5740
5741                     if (typeof ret != 'undefined') {
5742                         this[pName] = ret;
5743                     }
5744
5745                     return this;
5746                 };
5747             }
5748
5749             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5750                 data[getter] = function() {
5751                     return this[pName];
5752                 };
5753             }
5754         });
5755
5756         Ext.Object.merge(prototype.config, data.config);
5757         delete data.config;
5758     });
5759     //</feature>
5760
5761     //<feature classSystem.mixins>
5762     /**
5763      * @cfg {Object} mixins
5764      * List of classes to mix into this class. For example:
5765      *
5766      *     Ext.define('CanSing', {
5767      *          sing: function() {
5768      *              alert("I'm on the highway to hell...")
5769      *          }
5770      *     });
5771      *
5772      *     Ext.define('Musician', {
5773      *          extend: 'Person',
5774      *
5775      *          mixins: {
5776      *              canSing: 'CanSing'
5777      *          }
5778      *     })
5779      */
5780     Class.registerPreprocessor('mixins', function(cls, data) {
5781         var mixins = data.mixins,
5782             name, mixin, i, ln;
5783
5784         delete data.mixins;
5785
5786         Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
5787             if (mixins instanceof Array) {
5788                 for (i = 0,ln = mixins.length; i < ln; i++) {
5789                     mixin = mixins[i];
5790                     name = mixin.prototype.mixinId || mixin.$className;
5791
5792                     cls.mixin(name, mixin);
5793                 }
5794             }
5795             else {
5796                 for (name in mixins) {
5797                     if (mixins.hasOwnProperty(name)) {
5798                         cls.mixin(name, mixins[name]);
5799                     }
5800                 }
5801             }
5802         });
5803     });
5804
5805     //</feature>
5806
5807     Class.setDefaultPreprocessors([
5808         'extend'
5809         //<feature classSystem.statics>
5810         ,'statics'
5811         //</feature>
5812         //<feature classSystem.inheritableStatics>
5813         ,'inheritableStatics'
5814         //</feature>
5815         //<feature classSystem.config>
5816         ,'config'
5817         //</feature>
5818         //<feature classSystem.mixins>
5819         ,'mixins'
5820         //</feature>
5821     ]);
5822
5823     //<feature classSystem.backwardsCompatible>
5824     // Backwards compatible
5825     Ext.extend = function(subclass, superclass, members) {
5826         if (arguments.length === 2 && Ext.isObject(superclass)) {
5827             members = superclass;
5828             superclass = subclass;
5829             subclass = null;
5830         }
5831
5832         var cls;
5833
5834         if (!superclass) {
5835             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5836         }
5837
5838         members.extend = superclass;
5839         members.preprocessors = [
5840             'extend'
5841             //<feature classSystem.statics>
5842             ,'statics'
5843             //</feature>
5844             //<feature classSystem.inheritableStatics>
5845             ,'inheritableStatics'
5846             //</feature>
5847             //<feature classSystem.mixins>
5848             ,'mixins'
5849             //</feature>
5850             //<feature classSystem.config>
5851             ,'config'
5852             //</feature>
5853         ];
5854
5855         if (subclass) {
5856             cls = new Class(subclass, members);
5857         }
5858         else {
5859             cls = new Class(members);
5860         }
5861
5862         cls.prototype.override = function(o) {
5863             for (var m in o) {
5864                 if (o.hasOwnProperty(m)) {
5865                     this[m] = o[m];
5866                 }
5867             }
5868         };
5869
5870         return cls;
5871     };
5872     //</feature>
5873
5874 })();
5875
5876 /**
5877  * @author Jacky Nguyen <jacky@sencha.com>
5878  * @docauthor Jacky Nguyen <jacky@sencha.com>
5879  * @class Ext.ClassManager
5880  *
5881  * Ext.ClassManager manages all classes and handles mapping from string class name to
5882  * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5883  * these convenient shorthands:
5884  *
5885  * - {@link Ext#define Ext.define}
5886  * - {@link Ext#create Ext.create}
5887  * - {@link Ext#widget Ext.widget}
5888  * - {@link Ext#getClass Ext.getClass}
5889  * - {@link Ext#getClassName Ext.getClassName}
5890  *
5891  * # Basic syntax:
5892  *
5893  *     Ext.define(className, properties);
5894  *
5895  * in which `properties` is an object represent a collection of properties that apply to the class. See
5896  * {@link Ext.ClassManager#create} for more detailed instructions.
5897  *
5898  *     Ext.define('Person', {
5899  *          name: 'Unknown',
5900  *
5901  *          constructor: function(name) {
5902  *              if (name) {
5903  *                  this.name = name;
5904  *              }
5905  *
5906  *              return this;
5907  *          },
5908  *
5909  *          eat: function(foodType) {
5910  *              alert("I'm eating: " + foodType);
5911  *
5912  *              return this;
5913  *          }
5914  *     });
5915  *
5916  *     var aaron = new Person("Aaron");
5917  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5918  *
5919  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5920  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5921  *
5922  * # Inheritance:
5923  *
5924  *     Ext.define('Developer', {
5925  *          extend: 'Person',
5926  *
5927  *          constructor: function(name, isGeek) {
5928  *              this.isGeek = isGeek;
5929  *
5930  *              // Apply a method from the parent class' prototype
5931  *              this.callParent([name]);
5932  *
5933  *              return this;
5934  *
5935  *          },
5936  *
5937  *          code: function(language) {
5938  *              alert("I'm coding in: " + language);
5939  *
5940  *              this.eat("Bugs");
5941  *
5942  *              return this;
5943  *          }
5944  *     });
5945  *
5946  *     var jacky = new Developer("Jacky", true);
5947  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5948  *                               // alert("I'm eating: Bugs");
5949  *
5950  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5951  *
5952  * # Mixins:
5953  *
5954  *     Ext.define('CanPlayGuitar', {
5955  *          playGuitar: function() {
5956  *             alert("F#...G...D...A");
5957  *          }
5958  *     });
5959  *
5960  *     Ext.define('CanComposeSongs', {
5961  *          composeSongs: function() { ... }
5962  *     });
5963  *
5964  *     Ext.define('CanSing', {
5965  *          sing: function() {
5966  *              alert("I'm on the highway to hell...")
5967  *          }
5968  *     });
5969  *
5970  *     Ext.define('Musician', {
5971  *          extend: 'Person',
5972  *
5973  *          mixins: {
5974  *              canPlayGuitar: 'CanPlayGuitar',
5975  *              canComposeSongs: 'CanComposeSongs',
5976  *              canSing: 'CanSing'
5977  *          }
5978  *     })
5979  *
5980  *     Ext.define('CoolPerson', {
5981  *          extend: 'Person',
5982  *
5983  *          mixins: {
5984  *              canPlayGuitar: 'CanPlayGuitar',
5985  *              canSing: 'CanSing'
5986  *          },
5987  *
5988  *          sing: function() {
5989  *              alert("Ahem....");
5990  *
5991  *              this.mixins.canSing.sing.call(this);
5992  *
5993  *              alert("[Playing guitar at the same time...]");
5994  *
5995  *              this.playGuitar();
5996  *          }
5997  *     });
5998  *
5999  *     var me = new CoolPerson("Jacky");
6000  *
6001  *     me.sing(); // alert("Ahem...");
6002  *                // alert("I'm on the highway to hell...");
6003  *                // alert("[Playing guitar at the same time...]");
6004  *                // alert("F#...G...D...A");
6005  *
6006  * # Config:
6007  *
6008  *     Ext.define('SmartPhone', {
6009  *          config: {
6010  *              hasTouchScreen: false,
6011  *              operatingSystem: 'Other',
6012  *              price: 500
6013  *          },
6014  *
6015  *          isExpensive: false,
6016  *
6017  *          constructor: function(config) {
6018  *              this.initConfig(config);
6019  *
6020  *              return this;
6021  *          },
6022  *
6023  *          applyPrice: function(price) {
6024  *              this.isExpensive = (price > 500);
6025  *
6026  *              return price;
6027  *          },
6028  *
6029  *          applyOperatingSystem: function(operatingSystem) {
6030  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
6031  *                  return 'Other';
6032  *              }
6033  *
6034  *              return operatingSystem;
6035  *          }
6036  *     });
6037  *
6038  *     var iPhone = new SmartPhone({
6039  *          hasTouchScreen: true,
6040  *          operatingSystem: 'iOS'
6041  *     });
6042  *
6043  *     iPhone.getPrice(); // 500;
6044  *     iPhone.getOperatingSystem(); // 'iOS'
6045  *     iPhone.getHasTouchScreen(); // true;
6046  *     iPhone.hasTouchScreen(); // true
6047  *
6048  *     iPhone.isExpensive; // false;
6049  *     iPhone.setPrice(600);
6050  *     iPhone.getPrice(); // 600
6051  *     iPhone.isExpensive; // true;
6052  *
6053  *     iPhone.setOperatingSystem('AlienOS');
6054  *     iPhone.getOperatingSystem(); // 'Other'
6055  *
6056  * # Statics:
6057  *
6058  *     Ext.define('Computer', {
6059  *          statics: {
6060  *              factory: function(brand) {
6061  *                 // 'this' in static methods refer to the class itself
6062  *                  return new this(brand);
6063  *              }
6064  *          },
6065  *
6066  *          constructor: function() { ... }
6067  *     });
6068  *
6069  *     var dellComputer = Computer.factory('Dell');
6070  *
6071  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
6072  * static properties within class methods
6073  *
6074  * @singleton
6075  */
6076 (function(Class, alias) {
6077
6078     var slice = Array.prototype.slice;
6079
6080     var Manager = Ext.ClassManager = {
6081
6082         /**
6083          * @property {Object} classes
6084          * All classes which were defined through the ClassManager. Keys are the
6085          * name of the classes and the values are references to the classes.
6086          * @private
6087          */
6088         classes: {},
6089
6090         /**
6091          * @private
6092          */
6093         existCache: {},
6094
6095         /**
6096          * @private
6097          */
6098         namespaceRewrites: [{
6099             from: 'Ext.',
6100             to: Ext
6101         }],
6102
6103         /**
6104          * @private
6105          */
6106         maps: {
6107             alternateToName: {},
6108             aliasToName: {},
6109             nameToAliases: {}
6110         },
6111
6112         /** @private */
6113         enableNamespaceParseCache: true,
6114
6115         /** @private */
6116         namespaceParseCache: {},
6117
6118         /** @private */
6119         instantiators: [],
6120
6121
6122         /**
6123          * Checks if a class has already been created.
6124          *
6125          * @param {String} className
6126          * @return {Boolean} exist
6127          */
6128         isCreated: function(className) {
6129             var i, ln, part, root, parts;
6130
6131
6132             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6133                 return true;
6134             }
6135
6136             root = Ext.global;
6137             parts = this.parseNamespace(className);
6138
6139             for (i = 0, ln = parts.length; i < ln; i++) {
6140                 part = parts[i];
6141
6142                 if (typeof part !== 'string') {
6143                     root = part;
6144                 } else {
6145                     if (!root || !root[part]) {
6146                         return false;
6147                     }
6148
6149                     root = root[part];
6150                 }
6151             }
6152
6153             Ext.Loader.historyPush(className);
6154
6155             this.existCache[className] = true;
6156
6157             return true;
6158         },
6159
6160         /**
6161          * Supports namespace rewriting
6162          * @private
6163          */
6164         parseNamespace: function(namespace) {
6165
6166             var cache = this.namespaceParseCache;
6167
6168             if (this.enableNamespaceParseCache) {
6169                 if (cache.hasOwnProperty(namespace)) {
6170                     return cache[namespace];
6171                 }
6172             }
6173
6174             var parts = [],
6175                 rewrites = this.namespaceRewrites,
6176                 rewrite, from, to, i, ln, root = Ext.global;
6177
6178             for (i = 0, ln = rewrites.length; i < ln; i++) {
6179                 rewrite = rewrites[i];
6180                 from = rewrite.from;
6181                 to = rewrite.to;
6182
6183                 if (namespace === from || namespace.substring(0, from.length) === from) {
6184                     namespace = namespace.substring(from.length);
6185
6186                     if (typeof to !== 'string') {
6187                         root = to;
6188                     } else {
6189                         parts = parts.concat(to.split('.'));
6190                     }
6191
6192                     break;
6193                 }
6194             }
6195
6196             parts.push(root);
6197
6198             parts = parts.concat(namespace.split('.'));
6199
6200             if (this.enableNamespaceParseCache) {
6201                 cache[namespace] = parts;
6202             }
6203
6204             return parts;
6205         },
6206
6207         /**
6208          * Creates a namespace and assign the `value` to the created object
6209          *
6210          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6211          *
6212          *     alert(MyCompany.pkg.Example === someObject); // alerts true
6213          *
6214          * @param {String} name
6215          * @param {Object} value
6216          */
6217         setNamespace: function(name, value) {
6218             var root = Ext.global,
6219                 parts = this.parseNamespace(name),
6220                 ln = parts.length - 1,
6221                 leaf = parts[ln],
6222                 i, part;
6223
6224             for (i = 0; i < ln; i++) {
6225                 part = parts[i];
6226
6227                 if (typeof part !== 'string') {
6228                     root = part;
6229                 } else {
6230                     if (!root[part]) {
6231                         root[part] = {};
6232                     }
6233
6234                     root = root[part];
6235                 }
6236             }
6237
6238             root[leaf] = value;
6239
6240             return root[leaf];
6241         },
6242
6243         /**
6244          * The new Ext.ns, supports namespace rewriting
6245          * @private
6246          */
6247         createNamespaces: function() {
6248             var root = Ext.global,
6249                 parts, part, i, j, ln, subLn;
6250
6251             for (i = 0, ln = arguments.length; i < ln; i++) {
6252                 parts = this.parseNamespace(arguments[i]);
6253
6254                 for (j = 0, subLn = parts.length; j < subLn; j++) {
6255                     part = parts[j];
6256
6257                     if (typeof part !== 'string') {
6258                         root = part;
6259                     } else {
6260                         if (!root[part]) {
6261                             root[part] = {};
6262                         }
6263
6264                         root = root[part];
6265                     }
6266                 }
6267             }
6268
6269             return root;
6270         },
6271
6272         /**
6273          * Sets a name reference to a class.
6274          *
6275          * @param {String} name
6276          * @param {Object} value
6277          * @return {Ext.ClassManager} this
6278          */
6279         set: function(name, value) {
6280             var targetName = this.getName(value);
6281
6282             this.classes[name] = this.setNamespace(name, value);
6283
6284             if (targetName && targetName !== name) {
6285                 this.maps.alternateToName[name] = targetName;
6286             }
6287
6288             return this;
6289         },
6290
6291         /**
6292          * Retrieve a class by its name.
6293          *
6294          * @param {String} name
6295          * @return {Ext.Class} class
6296          */
6297         get: function(name) {
6298             if (this.classes.hasOwnProperty(name)) {
6299                 return this.classes[name];
6300             }
6301
6302             var root = Ext.global,
6303                 parts = this.parseNamespace(name),
6304                 part, i, ln;
6305
6306             for (i = 0, ln = parts.length; i < ln; i++) {
6307                 part = parts[i];
6308
6309                 if (typeof part !== 'string') {
6310                     root = part;
6311                 } else {
6312                     if (!root || !root[part]) {
6313                         return null;
6314                     }
6315
6316                     root = root[part];
6317                 }
6318             }
6319
6320             return root;
6321         },
6322
6323         /**
6324          * Register the alias for a class.
6325          *
6326          * @param {Ext.Class/String} cls a reference to a class or a className
6327          * @param {String} alias Alias to use when referring to this class
6328          */
6329         setAlias: function(cls, alias) {
6330             var aliasToNameMap = this.maps.aliasToName,
6331                 nameToAliasesMap = this.maps.nameToAliases,
6332                 className;
6333
6334             if (typeof cls === 'string') {
6335                 className = cls;
6336             } else {
6337                 className = this.getName(cls);
6338             }
6339
6340             if (alias && aliasToNameMap[alias] !== className) {
6341
6342                 aliasToNameMap[alias] = className;
6343             }
6344
6345             if (!nameToAliasesMap[className]) {
6346                 nameToAliasesMap[className] = [];
6347             }
6348
6349             if (alias) {
6350                 Ext.Array.include(nameToAliasesMap[className], alias);
6351             }
6352
6353             return this;
6354         },
6355
6356         /**
6357          * Get a reference to the class by its alias.
6358          *
6359          * @param {String} alias
6360          * @return {Ext.Class} class
6361          */
6362         getByAlias: function(alias) {
6363             return this.get(this.getNameByAlias(alias));
6364         },
6365
6366         /**
6367          * Get the name of a class by its alias.
6368          *
6369          * @param {String} alias
6370          * @return {String} className
6371          */
6372         getNameByAlias: function(alias) {
6373             return this.maps.aliasToName[alias] || '';
6374         },
6375
6376         /**
6377          * Get the name of a class by its alternate name.
6378          *
6379          * @param {String} alternate
6380          * @return {String} className
6381          */
6382         getNameByAlternate: function(alternate) {
6383             return this.maps.alternateToName[alternate] || '';
6384         },
6385
6386         /**
6387          * Get the aliases of a class by the class name
6388          *
6389          * @param {String} name
6390          * @return {String[]} aliases
6391          */
6392         getAliasesByName: function(name) {
6393             return this.maps.nameToAliases[name] || [];
6394         },
6395
6396         /**
6397          * Get the name of the class by its reference or its instance.
6398          *
6399          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6400          *
6401          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6402          *
6403          * @param {Ext.Class/Object} object
6404          * @return {String} className
6405          */
6406         getName: function(object) {
6407             return object && object.$className || '';
6408         },
6409
6410         /**
6411          * Get the class of the provided object; returns null if it's not an instance
6412          * of any class created with Ext.define.
6413          *
6414          *     var component = new Ext.Component();
6415          *
6416          *     Ext.ClassManager.getClass(component); // returns Ext.Component
6417          *
6418          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6419          *
6420          * @param {Object} object
6421          * @return {Ext.Class} class
6422          */
6423         getClass: function(object) {
6424             return object && object.self || null;
6425         },
6426
6427         /**
6428          * Defines a class.
6429          *
6430          * {@link Ext#define Ext.define} and {@link Ext.ClassManager#create Ext.ClassManager.create} are almost aliases
6431          * of each other, with the only exception that Ext.define allows definition of {@link Ext.Class#override overrides}.
6432          * To avoid trouble, always use Ext.define.
6433          *
6434          *     Ext.define('My.awesome.Class', {
6435          *         someProperty: 'something',
6436          *         someMethod: function() { ... }
6437          *         ...
6438          *
6439          *     }, function() {
6440          *         alert('Created!');
6441          *         alert(this === My.awesome.Class); // alerts true
6442          *
6443          *         var myInstance = new this();
6444          *     });
6445          *
6446          * @param {String} className The class name to create in string dot-namespaced format, for example:
6447          * `My.very.awesome.Class`, `FeedViewer.plugin.CoolPager`. It is highly recommended to follow this simple convention:
6448          *
6449          * - The root and the class name are 'CamelCased'
6450          * - Everything else is lower-cased
6451          *
6452          * @param {Object} data The key-value pairs of properties to apply to this class. Property names can be of any valid
6453          * strings, except those in the reserved list below:
6454          *
6455          * - {@link Ext.Base#self self}
6456          * - {@link Ext.Class#alias alias}
6457          * - {@link Ext.Class#alternateClassName alternateClassName}
6458          * - {@link Ext.Class#config config}
6459          * - {@link Ext.Class#extend extend}
6460          * - {@link Ext.Class#inheritableStatics inheritableStatics}
6461          * - {@link Ext.Class#mixins mixins}
6462          * - {@link Ext.Class#override override} (only when using {@link Ext#define Ext.define})
6463          * - {@link Ext.Class#requires requires}
6464          * - {@link Ext.Class#singleton singleton}
6465          * - {@link Ext.Class#statics statics}
6466          * - {@link Ext.Class#uses uses}
6467          *
6468          * @param {Function} [createdFn] callback to execute after the class is created, the execution scope of which
6469          * (`this`) will be the newly created class itself.
6470          *
6471          * @return {Ext.Base}
6472          */
6473         create: function(className, data, createdFn) {
6474             var manager = this;
6475
6476
6477             data.$className = className;
6478
6479             return new Class(data, function() {
6480                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6481                     registeredPostprocessors = manager.postprocessors,
6482                     index = 0,
6483                     postprocessors = [],
6484                     postprocessor, process, i, ln;
6485
6486                 delete data.postprocessors;
6487
6488                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6489                     postprocessor = postprocessorStack[i];
6490
6491                     if (typeof postprocessor === 'string') {
6492                         postprocessor = registeredPostprocessors[postprocessor];
6493
6494                         if (!postprocessor.always) {
6495                             if (data[postprocessor.name] !== undefined) {
6496                                 postprocessors.push(postprocessor.fn);
6497                             }
6498                         }
6499                         else {
6500                             postprocessors.push(postprocessor.fn);
6501                         }
6502                     }
6503                     else {
6504                         postprocessors.push(postprocessor);
6505                     }
6506                 }
6507
6508                 process = function(clsName, cls, clsData) {
6509                     postprocessor = postprocessors[index++];
6510
6511                     if (!postprocessor) {
6512                         manager.set(className, cls);
6513
6514                         Ext.Loader.historyPush(className);
6515
6516                         if (createdFn) {
6517                             createdFn.call(cls, cls);
6518                         }
6519
6520                         return;
6521                     }
6522
6523                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6524                         process.apply(this, arguments);
6525                     }
6526                 };
6527
6528                 process.call(manager, className, this, data);
6529             });
6530         },
6531
6532         /**
6533          * Instantiate a class by its alias.
6534          *
6535          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6536          * attempt to load the class via synchronous loading.
6537          *
6538          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6539          *
6540          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6541          *
6542          * @param {String} alias
6543          * @param {Object...} args Additional arguments after the alias will be passed to the
6544          * class constructor.
6545          * @return {Object} instance
6546          */
6547         instantiateByAlias: function() {
6548             var alias = arguments[0],
6549                 args = slice.call(arguments),
6550                 className = this.getNameByAlias(alias);
6551
6552             if (!className) {
6553                 className = this.maps.aliasToName[alias];
6554
6555
6556
6557                 Ext.syncRequire(className);
6558             }
6559
6560             args[0] = className;
6561
6562             return this.instantiate.apply(this, args);
6563         },
6564
6565         /**
6566          * Instantiate a class by either full name, alias or alternate name.
6567          *
6568          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6569          * attempt to load the class via synchronous loading.
6570          *
6571          * For example, all these three lines return the same result:
6572          *
6573          *     // alias
6574          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6575          *
6576          *     // alternate name
6577          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6578          *
6579          *     // full class name
6580          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6581          *
6582          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6583          *
6584          * @param {String} name
6585          * @param {Object...} args Additional arguments after the name will be passed to the class' constructor.
6586          * @return {Object} instance
6587          */
6588         instantiate: function() {
6589             var name = arguments[0],
6590                 args = slice.call(arguments, 1),
6591                 alias = name,
6592                 possibleName, cls;
6593
6594             if (typeof name !== 'function') {
6595
6596                 cls = this.get(name);
6597             }
6598             else {
6599                 cls = name;
6600             }
6601
6602             // No record of this class name, it's possibly an alias, so look it up
6603             if (!cls) {
6604                 possibleName = this.getNameByAlias(name);
6605
6606                 if (possibleName) {
6607                     name = possibleName;
6608
6609                     cls = this.get(name);
6610                 }
6611             }
6612
6613             // Still no record of this class name, it's possibly an alternate name, so look it up
6614             if (!cls) {
6615                 possibleName = this.getNameByAlternate(name);
6616
6617                 if (possibleName) {
6618                     name = possibleName;
6619
6620                     cls = this.get(name);
6621                 }
6622             }
6623
6624             // Still not existing at this point, try to load it via synchronous mode as the last resort
6625             if (!cls) {
6626
6627                 Ext.syncRequire(name);
6628
6629                 cls = this.get(name);
6630             }
6631
6632
6633
6634             return this.getInstantiator(args.length)(cls, args);
6635         },
6636
6637         /**
6638          * @private
6639          * @param name
6640          * @param args
6641          */
6642         dynInstantiate: function(name, args) {
6643             args = Ext.Array.from(args, true);
6644             args.unshift(name);
6645
6646             return this.instantiate.apply(this, args);
6647         },
6648
6649         /**
6650          * @private
6651          * @param length
6652          */
6653         getInstantiator: function(length) {
6654             if (!this.instantiators[length]) {
6655                 var i = length,
6656                     args = [];
6657
6658                 for (i = 0; i < length; i++) {
6659                     args.push('a['+i+']');
6660                 }
6661
6662                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6663             }
6664
6665             return this.instantiators[length];
6666         },
6667
6668         /**
6669          * @private
6670          */
6671         postprocessors: {},
6672
6673         /**
6674          * @private
6675          */
6676         defaultPostprocessors: [],
6677
6678         /**
6679          * Register a post-processor function.
6680          *
6681          * @param {String} name
6682          * @param {Function} postprocessor
6683          */
6684         registerPostprocessor: function(name, fn, always) {
6685             this.postprocessors[name] = {
6686                 name: name,
6687                 always: always ||  false,
6688                 fn: fn
6689             };
6690
6691             return this;
6692         },
6693
6694         /**
6695          * Set the default post processors array stack which are applied to every class.
6696          *
6697          * @param {String/String[]} The name of a registered post processor or an array of registered names.
6698          * @return {Ext.ClassManager} this
6699          */
6700         setDefaultPostprocessors: function(postprocessors) {
6701             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6702
6703             return this;
6704         },
6705
6706         /**
6707          * Insert this post-processor at a specific position in the stack, optionally relative to
6708          * any existing post-processor
6709          *
6710          * @param {String} name The post-processor name. Note that it needs to be registered with
6711          * {@link Ext.ClassManager#registerPostprocessor} before this
6712          * @param {String} offset The insertion position. Four possible values are:
6713          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6714          * @param {String} relativeName
6715          * @return {Ext.ClassManager} this
6716          */
6717         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6718             var defaultPostprocessors = this.defaultPostprocessors,
6719                 index;
6720
6721             if (typeof offset === 'string') {
6722                 if (offset === 'first') {
6723                     defaultPostprocessors.unshift(name);
6724
6725                     return this;
6726                 }
6727                 else if (offset === 'last') {
6728                     defaultPostprocessors.push(name);
6729
6730                     return this;
6731                 }
6732
6733                 offset = (offset === 'after') ? 1 : -1;
6734             }
6735
6736             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6737
6738             if (index !== -1) {
6739                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6740             }
6741
6742             return this;
6743         },
6744
6745         /**
6746          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6747          * or class names. Expressions support wildcards:
6748          *
6749          *     // returns ['Ext.window.Window']
6750          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6751          *
6752          *     // returns ['widget.panel', 'widget.window', ...]
6753          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6754          *
6755          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6756          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6757          *
6758          * @param {String} expression
6759          * @return {String[]} classNames
6760          */
6761         getNamesByExpression: function(expression) {
6762             var nameToAliasesMap = this.maps.nameToAliases,
6763                 names = [],
6764                 name, alias, aliases, possibleName, regex, i, ln;
6765
6766
6767             if (expression.indexOf('*') !== -1) {
6768                 expression = expression.replace(/\*/g, '(.*?)');
6769                 regex = new RegExp('^' + expression + '$');
6770
6771                 for (name in nameToAliasesMap) {
6772                     if (nameToAliasesMap.hasOwnProperty(name)) {
6773                         aliases = nameToAliasesMap[name];
6774
6775                         if (name.search(regex) !== -1) {
6776                             names.push(name);
6777                         }
6778                         else {
6779                             for (i = 0, ln = aliases.length; i < ln; i++) {
6780                                 alias = aliases[i];
6781
6782                                 if (alias.search(regex) !== -1) {
6783                                     names.push(name);
6784                                     break;
6785                                 }
6786                             }
6787                         }
6788                     }
6789                 }
6790
6791             } else {
6792                 possibleName = this.getNameByAlias(expression);
6793
6794                 if (possibleName) {
6795                     names.push(possibleName);
6796                 } else {
6797                     possibleName = this.getNameByAlternate(expression);
6798
6799                     if (possibleName) {
6800                         names.push(possibleName);
6801                     } else {
6802                         names.push(expression);
6803                     }
6804                 }
6805             }
6806
6807             return names;
6808         }
6809     };
6810
6811     var defaultPostprocessors = Manager.defaultPostprocessors;
6812     //<feature classSystem.alias>
6813
6814     /**
6815      * @cfg {String[]} alias
6816      * @member Ext.Class
6817      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
6818      *
6819      *     Ext.define('MyApp.CoolPanel', {
6820      *         extend: 'Ext.panel.Panel',
6821      *         alias: ['widget.coolpanel'],
6822      *         title: 'Yeah!'
6823      *     });
6824      *
6825      *     // Using Ext.create
6826      *     Ext.widget('widget.coolpanel');
6827      *     // Using the shorthand for widgets and in xtypes
6828      *     Ext.widget('panel', {
6829      *         items: [
6830      *             {xtype: 'coolpanel', html: 'Foo'},
6831      *             {xtype: 'coolpanel', html: 'Bar'}
6832      *         ]
6833      *     });
6834      */
6835     Manager.registerPostprocessor('alias', function(name, cls, data) {
6836         var aliases = data.alias,
6837             i, ln;
6838
6839         delete data.alias;
6840
6841         for (i = 0, ln = aliases.length; i < ln; i++) {
6842             alias = aliases[i];
6843
6844             this.setAlias(cls, alias);
6845         }
6846     });
6847
6848     /**
6849      * @cfg {Boolean} singleton
6850      * @member Ext.Class
6851      * When set to true, the class will be instantiated as singleton.  For example:
6852      *
6853      *     Ext.define('Logger', {
6854      *         singleton: true,
6855      *         log: function(msg) {
6856      *             console.log(msg);
6857      *         }
6858      *     });
6859      *
6860      *     Logger.log('Hello');
6861      */
6862     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6863         fn.call(this, name, new cls(), data);
6864         return false;
6865     });
6866
6867     /**
6868      * @cfg {String/String[]} alternateClassName
6869      * @member Ext.Class
6870      * Defines alternate names for this class.  For example:
6871      *
6872      *     Ext.define('Developer', {
6873      *         alternateClassName: ['Coder', 'Hacker'],
6874      *         code: function(msg) {
6875      *             alert('Typing... ' + msg);
6876      *         }
6877      *     });
6878      *
6879      *     var joe = Ext.create('Developer');
6880      *     joe.code('stackoverflow');
6881      *
6882      *     var rms = Ext.create('Hacker');
6883      *     rms.code('hack hack');
6884      */
6885     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6886         var alternates = data.alternateClassName,
6887             i, ln, alternate;
6888
6889         if (!(alternates instanceof Array)) {
6890             alternates = [alternates];
6891         }
6892
6893         for (i = 0, ln = alternates.length; i < ln; i++) {
6894             alternate = alternates[i];
6895
6896
6897             this.set(alternate, cls);
6898         }
6899     });
6900
6901     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6902
6903     Ext.apply(Ext, {
6904         /**
6905          * @method
6906          * @member Ext
6907          * @alias Ext.ClassManager#instantiate
6908          */
6909         create: alias(Manager, 'instantiate'),
6910
6911         /**
6912          * @private
6913          * API to be stablized
6914          *
6915          * @param {Object} item
6916          * @param {String} namespace
6917          */
6918         factory: function(item, namespace) {
6919             if (item instanceof Array) {
6920                 var i, ln;
6921
6922                 for (i = 0, ln = item.length; i < ln; i++) {
6923                     item[i] = Ext.factory(item[i], namespace);
6924                 }
6925
6926                 return item;
6927             }
6928
6929             var isString = (typeof item === 'string');
6930
6931             if (isString || (item instanceof Object && item.constructor === Object)) {
6932                 var name, config = {};
6933
6934                 if (isString) {
6935                     name = item;
6936                 }
6937                 else {
6938                     name = item.className;
6939                     config = item;
6940                     delete config.className;
6941                 }
6942
6943                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6944                     name = namespace + '.' + Ext.String.capitalize(name);
6945                 }
6946
6947                 return Ext.create(name, config);
6948             }
6949
6950             if (typeof item === 'function') {
6951                 return Ext.create(item);
6952             }
6953
6954             return item;
6955         },
6956
6957         /**
6958          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6959          *
6960          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6961          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6962          *
6963          * @method
6964          * @member Ext
6965          * @param {String} name  xtype of the widget to create.
6966          * @param {Object...} args  arguments for the widget constructor.
6967          * @return {Object} widget instance
6968          */
6969         widget: function(name) {
6970             var args = slice.call(arguments);
6971             args[0] = 'widget.' + name;
6972
6973             return Manager.instantiateByAlias.apply(Manager, args);
6974         },
6975
6976         /**
6977          * @method
6978          * @member Ext
6979          * @alias Ext.ClassManager#instantiateByAlias
6980          */
6981         createByAlias: alias(Manager, 'instantiateByAlias'),
6982
6983         /**
6984          * @cfg {String} override
6985          * @member Ext.Class
6986          * 
6987          * Defines an override applied to a class. Note that **overrides can only be created using
6988          * {@link Ext#define}.** {@link Ext.ClassManager#create} only creates classes.
6989          * 
6990          * To define an override, include the override property. The content of an override is
6991          * aggregated with the specified class in order to extend or modify that class. This can be
6992          * as simple as setting default property values or it can extend and/or replace methods.
6993          * This can also extend the statics of the class.
6994          *
6995          * One use for an override is to break a large class into manageable pieces.
6996          *
6997          *      // File: /src/app/Panel.js
6998          *
6999          *      Ext.define('My.app.Panel', {
7000          *          extend: 'Ext.panel.Panel',
7001          *          requires: [
7002          *              'My.app.PanelPart2',
7003          *              'My.app.PanelPart3'
7004          *          ]
7005          *
7006          *          constructor: function (config) {
7007          *              this.callSuper(arguments); // calls Ext.panel.Panel's constructor
7008          *              //...
7009          *          },
7010          *
7011          *          statics: {
7012          *              method: function () {
7013          *                  return 'abc';
7014          *              }
7015          *          }
7016          *      });
7017          *
7018          *      // File: /src/app/PanelPart2.js
7019          *      Ext.define('My.app.PanelPart2', {
7020          *          override: 'My.app.Panel',
7021          *
7022          *          constructor: function (config) {
7023          *              this.callSuper(arguments); // calls My.app.Panel's constructor
7024          *              //...
7025          *          }
7026          *      });
7027          *
7028          * Another use of overrides is to provide optional parts of classes that can be
7029          * independently required. In this case, the class may even be unaware of the
7030          * override altogether.
7031          *
7032          *      Ext.define('My.ux.CoolTip', {
7033          *          override: 'Ext.tip.ToolTip',
7034          *
7035          *          constructor: function (config) {
7036          *              this.callSuper(arguments); // calls Ext.tip.ToolTip's constructor
7037          *              //...
7038          *          }
7039          *      });
7040          *
7041          * The above override can now be required as normal.
7042          *
7043          *      Ext.define('My.app.App', {
7044          *          requires: [
7045          *              'My.ux.CoolTip'
7046          *          ]
7047          *      });
7048          *
7049          * Overrides can also contain statics:
7050          *
7051          *      Ext.define('My.app.BarMod', {
7052          *          override: 'Ext.foo.Bar',
7053          *
7054          *          statics: {
7055          *              method: function (x) {
7056          *                  return this.callSuper([x * 2]); // call Ext.foo.Bar.method
7057          *              }
7058          *          }
7059          *      });
7060          *
7061          * IMPORTANT: An override is only included in a build if the class it overrides is
7062          * required. Otherwise, the override, like the target class, is not included.
7063          */
7064         
7065         /**
7066          * @method
7067          *
7068          * @member Ext
7069          * @alias Ext.ClassManager#create
7070          */
7071         define: function (className, data, createdFn) {
7072             if (!data.override) {
7073                 return Manager.create.apply(Manager, arguments);
7074             }
7075
7076             var requires = data.requires,
7077                 uses = data.uses,
7078                 overrideName = className;
7079
7080             className = data.override;
7081
7082             // hoist any 'requires' or 'uses' from the body onto the faux class:
7083             data = Ext.apply({}, data);
7084             delete data.requires;
7085             delete data.uses;
7086             delete data.override;
7087
7088             // make sure className is in the requires list:
7089             if (typeof requires == 'string') {
7090                 requires = [ className, requires ];
7091             } else if (requires) {
7092                 requires = requires.slice(0);
7093                 requires.unshift(className);
7094             } else {
7095                 requires = [ className ];
7096             }
7097
7098 // TODO - we need to rework this to allow the override to not require the target class
7099 //  and rather 'wait' for it in such a way that if the target class is not in the build,
7100 //  neither are any of its overrides.
7101 //
7102 //  Also, this should process the overrides for a class ASAP (ideally before any derived
7103 //  classes) if the target class 'requires' the overrides. Without some special handling, the
7104 //  overrides so required will be processed before the class and have to be bufferred even
7105 //  in a build.
7106 //
7107 // TODO - we should probably support the "config" processor on an override (to config new
7108 //  functionaliy like Aria) and maybe inheritableStatics (although static is now supported
7109 //  by callSuper). If inheritableStatics causes those statics to be included on derived class
7110 //  constructors, that probably means "no" to this since an override can come after other
7111 //  classes extend the target.
7112             return Manager.create(overrideName, {
7113                     requires: requires,
7114                     uses: uses,
7115                     isPartial: true,
7116                     constructor: function () {
7117                     }
7118                 }, function () {
7119                     var cls = Manager.get(className);
7120                     if (cls.override) { // if (normal class)
7121                         cls.override(data);
7122                     } else { // else (singleton)
7123                         cls.self.override(data);
7124                     }
7125
7126                     if (createdFn) {
7127                         // called once the override is applied and with the context of the
7128                         // overridden class (the override itself is a meaningless, name-only
7129                         // thing).
7130                         createdFn.call(cls);
7131                     }
7132                 });
7133         },
7134
7135         /**
7136          * @method
7137          * @member Ext
7138          * @alias Ext.ClassManager#getName
7139          */
7140         getClassName: alias(Manager, 'getName'),
7141
7142         /**
7143          * Returns the displayName property or className or object.
7144          * When all else fails, returns "Anonymous".
7145          * @param {Object} object
7146          * @return {String}
7147          */
7148         getDisplayName: function(object) {
7149             if (object.displayName) {
7150                 return object.displayName;
7151             }
7152
7153             if (object.$name && object.$class) {
7154                 return Ext.getClassName(object.$class) + '#' + object.$name;
7155             }
7156
7157             if (object.$className) {
7158                 return object.$className;
7159             }
7160
7161             return 'Anonymous';
7162         },
7163
7164         /**
7165          * @method
7166          * @member Ext
7167          * @alias Ext.ClassManager#getClass
7168          */
7169         getClass: alias(Manager, 'getClass'),
7170
7171         /**
7172          * Creates namespaces to be used for scoping variables and classes so that they are not global.
7173          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
7174          *
7175          *     Ext.namespace('Company', 'Company.data');
7176          *
7177          *     // equivalent and preferable to the above syntax
7178          *     Ext.namespace('Company.data');
7179          *
7180          *     Company.Widget = function() { ... };
7181          *
7182          *     Company.data.CustomStore = function(config) { ... };
7183          *
7184          * @method
7185          * @member Ext
7186          * @param {String} namespace1
7187          * @param {String} namespace2
7188          * @param {String} etc
7189          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7190          */
7191         namespace: alias(Manager, 'createNamespaces')
7192     });
7193
7194     /**
7195      * Old name for {@link Ext#widget}.
7196      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7197      * @method
7198      * @member Ext
7199      * @alias Ext#widget
7200      */
7201     Ext.createWidget = Ext.widget;
7202
7203     /**
7204      * Convenient alias for {@link Ext#namespace Ext.namespace}
7205      * @method
7206      * @member Ext
7207      * @alias Ext#namespace
7208      */
7209     Ext.ns = Ext.namespace;
7210
7211     Class.registerPreprocessor('className', function(cls, data) {
7212         if (data.$className) {
7213             cls.$className = data.$className;
7214         }
7215     }, true);
7216
7217     Class.setDefaultPreprocessorPosition('className', 'first');
7218
7219     Class.registerPreprocessor('xtype', function(cls, data) {
7220         var xtypes = Ext.Array.from(data.xtype),
7221             widgetPrefix = 'widget.',
7222             aliases = Ext.Array.from(data.alias),
7223             i, ln, xtype;
7224
7225         data.xtype = xtypes[0];
7226         data.xtypes = xtypes;
7227
7228         aliases = data.alias = Ext.Array.from(data.alias);
7229
7230         for (i = 0,ln = xtypes.length; i < ln; i++) {
7231             xtype = xtypes[i];
7232
7233
7234             aliases.push(widgetPrefix + xtype);
7235         }
7236
7237         data.alias = aliases;
7238     });
7239
7240     Class.setDefaultPreprocessorPosition('xtype', 'last');
7241
7242     Class.registerPreprocessor('alias', function(cls, data) {
7243         var aliases = Ext.Array.from(data.alias),
7244             xtypes = Ext.Array.from(data.xtypes),
7245             widgetPrefix = 'widget.',
7246             widgetPrefixLength = widgetPrefix.length,
7247             i, ln, alias, xtype;
7248
7249         for (i = 0, ln = aliases.length; i < ln; i++) {
7250             alias = aliases[i];
7251
7252
7253             if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
7254                 xtype = alias.substring(widgetPrefixLength);
7255                 Ext.Array.include(xtypes, xtype);
7256
7257                 if (!cls.xtype) {
7258                     cls.xtype = data.xtype = xtype;
7259                 }
7260             }
7261         }
7262
7263         data.alias = aliases;
7264         data.xtypes = xtypes;
7265     });
7266
7267     Class.setDefaultPreprocessorPosition('alias', 'last');
7268
7269 })(Ext.Class, Ext.Function.alias);
7270
7271 /**
7272  * @class Ext.Loader
7273  * @singleton
7274  * @author Jacky Nguyen <jacky@sencha.com>
7275  * @docauthor Jacky Nguyen <jacky@sencha.com>
7276  *
7277  * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7278  * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7279  * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7280  * of each approach:
7281  *
7282  * # Asynchronous Loading
7283  *
7284  * - Advantages:
7285  *       + Cross-domain
7286  *       + No web server needed: you can run the application via the file system protocol
7287  *     (i.e: `file://path/to/your/index.html`)
7288  *       + Best possible debugging experience: error messages come with the exact file name and line number
7289  *
7290  * - Disadvantages:
7291  *       + Dependencies need to be specified before-hand
7292  *
7293  * ### Method 1: Explicitly include what you need:
7294  *
7295  *     // Syntax
7296  *     Ext.require({String/Array} expressions);
7297  *
7298  *     // Example: Single alias
7299  *     Ext.require('widget.window');
7300  *
7301  *     // Example: Single class name
7302  *     Ext.require('Ext.window.Window');
7303  *
7304  *     // Example: Multiple aliases / class names mix
7305  *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7306  *
7307  *     // Wildcards
7308  *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7309  *
7310  * ### Method 2: Explicitly exclude what you don't need:
7311  *
7312  *     // Syntax: Note that it must be in this chaining format.
7313  *     Ext.exclude({String/Array} expressions)
7314  *        .require({String/Array} expressions);
7315  *
7316  *     // Include everything except Ext.data.*
7317  *     Ext.exclude('Ext.data.*').require('*'); 
7318  *
7319  *     // Include all widgets except widget.checkbox*,
7320  *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7321  *     Ext.exclude('widget.checkbox*').require('widget.*');
7322  *
7323  * # Synchronous Loading on Demand
7324  *
7325  * - Advantages:
7326  *       + There's no need to specify dependencies before-hand, which is always the convenience of including
7327  *     ext-all.js before
7328  *
7329  * - Disadvantages:
7330  *       + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7331  *       + Must be from the same domain due to XHR restriction
7332  *       + Need a web server, same reason as above
7333  *
7334  * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7335  *
7336  *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7337  *
7338  *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7339  *
7340  *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7341  *
7342  * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7343  * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7344  * the given class and all its dependencies.
7345  *
7346  * # Hybrid Loading - The Best of Both Worlds
7347  *
7348  * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7349  *
7350  * ### Step 1: Start writing your application using synchronous approach.
7351  *
7352  * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7353  *
7354  *     Ext.onReady(function(){
7355  *         var window = Ext.createWidget('window', {
7356  *             width: 500,
7357  *             height: 300,
7358  *             layout: {
7359  *                 type: 'border',
7360  *                 padding: 5
7361  *             },
7362  *             title: 'Hello Dialog',
7363  *             items: [{
7364  *                 title: 'Navigation',
7365  *                 collapsible: true,
7366  *                 region: 'west',
7367  *                 width: 200,
7368  *                 html: 'Hello',
7369  *                 split: true
7370  *             }, {
7371  *                 title: 'TabPanel',
7372  *                 region: 'center'
7373  *             }]
7374  *         });
7375  *
7376  *         window.show();
7377  *     })
7378  *
7379  * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7380  *
7381  *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7382  *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7383  *
7384  * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7385  *
7386  *     Ext.require('Ext.window.Window');
7387  *     Ext.require('Ext.layout.container.Border');
7388  *
7389  *     Ext.onReady(...);
7390  *
7391  * Everything should now load via asynchronous mode.
7392  *
7393  * # Deployment
7394  *
7395  * It's important to note that dynamic loading should only be used during development on your local machines.
7396  * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7397  * the whole process of transitioning from / to between development / maintenance and production as easy as
7398  * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7399  * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7400  * array into one, then include it on top of your application.
7401  *
7402  * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7403  */
7404 (function(Manager, Class, flexSetter, alias) {
7405
7406     var
7407         dependencyProperties = ['extend', 'mixins', 'requires'],
7408         Loader;
7409
7410     Loader = Ext.Loader = {
7411         /**
7412          * @private
7413          */
7414         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7415
7416         /**
7417          * Flag indicating whether there are still files being loaded
7418          * @private
7419          */
7420         isLoading: false,
7421
7422         /**
7423          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7424          * {
7425          *      requires: [...], // The required classes for this queue item
7426          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
7427          * }
7428          * @private
7429          */
7430         queue: [],
7431
7432         /**
7433          * Maintain the list of files that have already been handled so that they never get double-loaded
7434          * @private
7435          */
7436         isFileLoaded: {},
7437
7438         /**
7439          * Maintain the list of listeners to execute when all required scripts are fully loaded
7440          * @private
7441          */
7442         readyListeners: [],
7443
7444         /**
7445          * Contains optional dependencies to be loaded last
7446          * @private
7447          */
7448         optionalRequires: [],
7449
7450         /**
7451          * Map of fully qualified class names to an array of dependent classes.
7452          * @private
7453          */
7454         requiresMap: {},
7455
7456         /**
7457          * @private
7458          */
7459         numPendingFiles: 0,
7460
7461         /**
7462          * @private
7463          */
7464         numLoadedFiles: 0,
7465
7466         /** @private */
7467         hasFileLoadError: false,
7468
7469         /**
7470          * @private
7471          */
7472         classNameToFilePathMap: {},
7473
7474         /**
7475          * @property {String[]} history
7476          * An array of class names to keep track of the dependency loading order.
7477          * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7478          */
7479         history: [],
7480
7481         /**
7482          * Configuration
7483          * @private
7484          */
7485         config: {
7486             /**
7487              * @cfg {Boolean} enabled
7488              * Whether or not to enable the dynamic dependency loading feature.
7489              */
7490             enabled: false,
7491
7492             /**
7493              * @cfg {Boolean} disableCaching
7494              * Appends current timestamp to script files to prevent caching.
7495              */
7496             disableCaching: true,
7497
7498             /**
7499              * @cfg {String} disableCachingParam
7500              * The get parameter name for the cache buster's timestamp.
7501              */
7502             disableCachingParam: '_dc',
7503
7504             /**
7505              * @cfg {Object} paths
7506              * The mapping from namespaces to file paths
7507              *
7508              *     {
7509              *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7510              *                     // loaded from ./layout/Container.js
7511              *
7512              *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
7513              *                                     // ./src/my_own_folder/layout/Container.js
7514              *     }
7515              *
7516              * Note that all relative paths are relative to the current HTML document.
7517              * If not being specified, for example, `Other.awesome.Class`
7518              * will simply be loaded from `./Other/awesome/Class.js`
7519              */
7520             paths: {
7521                 'Ext': '.'
7522             }
7523         },
7524
7525         /**
7526          * Set the configuration for the loader. This should be called right after ext-core.js
7527          * (or ext-core-debug.js) is included in the page, e.g.:
7528          *
7529          *     <script type="text/javascript" src="ext-core-debug.js"></script>
7530          *     <script type="text/javascript">
7531          *       Ext.Loader.setConfig({
7532          *           enabled: true,
7533          *           paths: {
7534          *               'My': 'my_own_path'
7535          *           }
7536          *       });
7537          *     <script>
7538          *     <script type="text/javascript">
7539          *       Ext.require(...);
7540          *
7541          *       Ext.onReady(function() {
7542          *           // application code here
7543          *       });
7544          *     </script>
7545          *
7546          * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7547          *
7548          * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
7549          * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
7550          * @return {Ext.Loader} this
7551          */
7552         setConfig: function(name, value) {
7553             if (Ext.isObject(name) && arguments.length === 1) {
7554                 Ext.Object.merge(this.config, name);
7555             }
7556             else {
7557                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7558             }
7559
7560             return this;
7561         },
7562
7563         /**
7564          * Get the config value corresponding to the specified name.
7565          * If no name is given, will return the config object.
7566          * @param {String} name The config property name
7567          * @return {Object}
7568          */
7569         getConfig: function(name) {
7570             if (name) {
7571                 return this.config[name];
7572             }
7573
7574             return this.config;
7575         },
7576
7577         /**
7578          * Sets the path of a namespace. For Example:
7579          *
7580          *     Ext.Loader.setPath('Ext', '.');
7581          *
7582          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7583          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7584          * @return {Ext.Loader} this
7585          * @method
7586          */
7587         setPath: flexSetter(function(name, path) {
7588             this.config.paths[name] = path;
7589
7590             return this;
7591         }),
7592
7593         /**
7594          * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7595          * For example:
7596          *
7597          *     Ext.Loader.setPath('My', '/path/to/My');
7598          *
7599          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7600          *
7601          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7602          *
7603          *     Ext.Loader.setPath({
7604          *         'My': '/path/to/lib',
7605          *         'My.awesome': '/other/path/for/awesome/stuff',
7606          *         'My.awesome.more': '/more/awesome/path'
7607          *     });
7608          *
7609          *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7610          *
7611          *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7612          *
7613          *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7614          *
7615          *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7616          *
7617          * @param {String} className
7618          * @return {String} path
7619          */
7620         getPath: function(className) {
7621             var path = '',
7622                 paths = this.config.paths,
7623                 prefix = this.getPrefix(className);
7624
7625             if (prefix.length > 0) {
7626                 if (prefix === className) {
7627                     return paths[prefix];
7628                 }
7629
7630                 path = paths[prefix];
7631                 className = className.substring(prefix.length + 1);
7632             }
7633
7634             if (path.length > 0) {
7635                 path += '/';
7636             }
7637
7638             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7639         },
7640
7641         /**
7642          * @private
7643          * @param {String} className
7644          */
7645         getPrefix: function(className) {
7646             var paths = this.config.paths,
7647                 prefix, deepestPrefix = '';
7648
7649             if (paths.hasOwnProperty(className)) {
7650                 return className;
7651             }
7652
7653             for (prefix in paths) {
7654                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7655                     if (prefix.length > deepestPrefix.length) {
7656                         deepestPrefix = prefix;
7657                     }
7658                 }
7659             }
7660
7661             return deepestPrefix;
7662         },
7663
7664         /**
7665          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7666          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7667          * empty
7668          * @private
7669          */
7670         refreshQueue: function() {
7671             var ln = this.queue.length,
7672                 i, item, j, requires;
7673
7674             if (ln === 0) {
7675                 this.triggerReady();
7676                 return;
7677             }
7678
7679             for (i = 0; i < ln; i++) {
7680                 item = this.queue[i];
7681
7682                 if (item) {
7683                     requires = item.requires;
7684
7685                     // Don't bother checking when the number of files loaded
7686                     // is still less than the array length
7687                     if (requires.length > this.numLoadedFiles) {
7688                         continue;
7689                     }
7690
7691                     j = 0;
7692
7693                     do {
7694                         if (Manager.isCreated(requires[j])) {
7695                             // Take out from the queue
7696                             Ext.Array.erase(requires, j, 1);
7697                         }
7698                         else {
7699                             j++;
7700                         }
7701                     } while (j < requires.length);
7702
7703                     if (item.requires.length === 0) {
7704                         Ext.Array.erase(this.queue, i, 1);
7705                         item.callback.call(item.scope);
7706                         this.refreshQueue();
7707                         break;
7708                     }
7709                 }
7710             }
7711
7712             return this;
7713         },
7714
7715         /**
7716          * Inject a script element to document's head, call onLoad and onError accordingly
7717          * @private
7718          */
7719         injectScriptElement: function(url, onLoad, onError, scope) {
7720             var script = document.createElement('script'),
7721                 me = this,
7722                 onLoadFn = function() {
7723                     me.cleanupScriptElement(script);
7724                     onLoad.call(scope);
7725                 },
7726                 onErrorFn = function() {
7727                     me.cleanupScriptElement(script);
7728                     onError.call(scope);
7729                 };
7730
7731             script.type = 'text/javascript';
7732             script.src = url;
7733             script.onload = onLoadFn;
7734             script.onerror = onErrorFn;
7735             script.onreadystatechange = function() {
7736                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7737                     onLoadFn();
7738                 }
7739             };
7740
7741             this.documentHead.appendChild(script);
7742
7743             return script;
7744         },
7745
7746         /**
7747          * @private
7748          */
7749         cleanupScriptElement: function(script) {
7750             script.onload = null;
7751             script.onreadystatechange = null;
7752             script.onerror = null;
7753
7754             return this;
7755         },
7756
7757         /**
7758          * Load a script file, supports both asynchronous and synchronous approaches
7759          *
7760          * @param {String} url
7761          * @param {Function} onLoad
7762          * @param {Object} scope
7763          * @param {Boolean} synchronous
7764          * @private
7765          */
7766         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7767             var me = this,
7768                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7769                 fileName = url.split('/').pop(),
7770                 isCrossOriginRestricted = false,
7771                 xhr, status, onScriptError;
7772
7773             scope = scope || this;
7774
7775             this.isLoading = true;
7776
7777             if (!synchronous) {
7778                 onScriptError = function() {
7779                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7780                 };
7781
7782                 if (!Ext.isReady && Ext.onDocumentReady) {
7783                     Ext.onDocumentReady(function() {
7784                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7785                     });
7786                 }
7787                 else {
7788                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7789                 }
7790             }
7791             else {
7792                 if (typeof XMLHttpRequest !== 'undefined') {
7793                     xhr = new XMLHttpRequest();
7794                 } else {
7795                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7796                 }
7797
7798                 try {
7799                     xhr.open('GET', noCacheUrl, false);
7800                     xhr.send(null);
7801                 } catch (e) {
7802                     isCrossOriginRestricted = true;
7803                 }
7804
7805                 status = (xhr.status === 1223) ? 204 : xhr.status;
7806
7807                 if (!isCrossOriginRestricted) {
7808                     isCrossOriginRestricted = (status === 0);
7809                 }
7810
7811                 if (isCrossOriginRestricted
7812                 ) {
7813                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7814                                        "being loaded from a different domain or from the local file system whereby cross origin " +
7815                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
7816                                        "Ext.require instead.", synchronous);
7817                 }
7818                 else if (status >= 200 && status < 300
7819                 ) {
7820                     // Firebug friendly, file names are still shown even though they're eval'ed code
7821                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7822
7823                     onLoad.call(scope);
7824                 }
7825                 else {
7826                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7827                                        "verify that the file exists. " +
7828                                        "XHR status code: " + status, synchronous);
7829                 }
7830
7831                 // Prevent potential IE memory leak
7832                 xhr = null;
7833             }
7834         },
7835
7836         /**
7837          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7838          * Can be chained with more `require` and `exclude` methods, e.g.:
7839          *
7840          *     Ext.exclude('Ext.data.*').require('*');
7841          *
7842          *     Ext.exclude('widget.button*').require('widget.*');
7843          *
7844          * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
7845          *
7846          * @param {String/String[]} excludes
7847          * @return {Object} object contains `require` method for chaining
7848          */
7849         exclude: function(excludes) {
7850             var me = this;
7851
7852             return {
7853                 require: function(expressions, fn, scope) {
7854                     return me.require(expressions, fn, scope, excludes);
7855                 },
7856
7857                 syncRequire: function(expressions, fn, scope) {
7858                     return me.syncRequire(expressions, fn, scope, excludes);
7859                 }
7860             };
7861         },
7862
7863         /**
7864          * Synchronously loads all classes by the given names and all their direct dependencies;
7865          * optionally executes the given callback function when finishes, within the optional scope.
7866          *
7867          * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
7868          *
7869          * @param {String/String[]} expressions Can either be a string or an array of string
7870          * @param {Function} fn (Optional) The callback function
7871          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7872          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7873          */
7874         syncRequire: function() {
7875             this.syncModeEnabled = true;
7876             this.require.apply(this, arguments);
7877             this.refreshQueue();
7878             this.syncModeEnabled = false;
7879         },
7880
7881         /**
7882          * Loads all classes by the given names and all their direct dependencies;
7883          * optionally executes the given callback function when finishes, within the optional scope.
7884          *
7885          * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
7886          *
7887          * @param {String/String[]} expressions Can either be a string or an array of string
7888          * @param {Function} fn (Optional) The callback function
7889          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7890          * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7891          */
7892         require: function(expressions, fn, scope, excludes) {
7893             var filePath, expression, exclude, className, excluded = {},
7894                 excludedClassNames = [],
7895                 possibleClassNames = [],
7896                 possibleClassName, classNames = [],
7897                 i, j, ln, subLn;
7898
7899             expressions = Ext.Array.from(expressions);
7900             excludes = Ext.Array.from(excludes);
7901
7902             fn = fn || Ext.emptyFn;
7903
7904             scope = scope || Ext.global;
7905
7906             for (i = 0, ln = excludes.length; i < ln; i++) {
7907                 exclude = excludes[i];
7908
7909                 if (typeof exclude === 'string' && exclude.length > 0) {
7910                     excludedClassNames = Manager.getNamesByExpression(exclude);
7911
7912                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7913                         excluded[excludedClassNames[j]] = true;
7914                     }
7915                 }
7916             }
7917
7918             for (i = 0, ln = expressions.length; i < ln; i++) {
7919                 expression = expressions[i];
7920
7921                 if (typeof expression === 'string' && expression.length > 0) {
7922                     possibleClassNames = Manager.getNamesByExpression(expression);
7923
7924                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7925                         possibleClassName = possibleClassNames[j];
7926
7927                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7928                             Ext.Array.include(classNames, possibleClassName);
7929                         }
7930                     }
7931                 }
7932             }
7933
7934             // If the dynamic dependency feature is not being used, throw an error
7935             // if the dependencies are not defined
7936             if (!this.config.enabled) {
7937                 if (classNames.length > 0) {
7938                     Ext.Error.raise({
7939                         sourceClass: "Ext.Loader",
7940                         sourceMethod: "require",
7941                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7942                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7943                     });
7944                 }
7945             }
7946
7947             if (classNames.length === 0) {
7948                 fn.call(scope);
7949                 return this;
7950             }
7951
7952             this.queue.push({
7953                 requires: classNames,
7954                 callback: fn,
7955                 scope: scope
7956             });
7957
7958             classNames = classNames.slice();
7959
7960             for (i = 0, ln = classNames.length; i < ln; i++) {
7961                 className = classNames[i];
7962
7963                 if (!this.isFileLoaded.hasOwnProperty(className)) {
7964                     this.isFileLoaded[className] = false;
7965
7966                     filePath = this.getPath(className);
7967
7968                     this.classNameToFilePathMap[className] = filePath;
7969
7970                     this.numPendingFiles++;
7971
7972                     this.loadScriptFile(
7973                         filePath,
7974                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7975                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7976                         this,
7977                         this.syncModeEnabled
7978                     );
7979                 }
7980             }
7981
7982             return this;
7983         },
7984
7985         /**
7986          * @private
7987          * @param {String} className
7988          * @param {String} filePath
7989          */
7990         onFileLoaded: function(className, filePath) {
7991             this.numLoadedFiles++;
7992
7993             this.isFileLoaded[className] = true;
7994
7995             this.numPendingFiles--;
7996
7997             if (this.numPendingFiles === 0) {
7998                 this.refreshQueue();
7999             }
8000
8001
8002         },
8003
8004         /**
8005          * @private
8006          */
8007         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
8008             this.numPendingFiles--;
8009             this.hasFileLoadError = true;
8010
8011         },
8012
8013         /**
8014          * @private
8015          */
8016         addOptionalRequires: function(requires) {
8017             var optionalRequires = this.optionalRequires,
8018                 i, ln, require;
8019
8020             requires = Ext.Array.from(requires);
8021
8022             for (i = 0, ln = requires.length; i < ln; i++) {
8023                 require = requires[i];
8024
8025                 Ext.Array.include(optionalRequires, require);
8026             }
8027
8028             return this;
8029         },
8030
8031         /**
8032          * @private
8033          */
8034         triggerReady: function(force) {
8035             var readyListeners = this.readyListeners,
8036                 optionalRequires, listener;
8037
8038             if (this.isLoading || force) {
8039                 this.isLoading = false;
8040
8041                 if (this.optionalRequires.length) {
8042                     // Clone then empty the array to eliminate potential recursive loop issue
8043                     optionalRequires = Ext.Array.clone(this.optionalRequires);
8044
8045                     // Empty the original array
8046                     this.optionalRequires.length = 0;
8047
8048                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
8049                     return this;
8050                 }
8051
8052                 while (readyListeners.length) {
8053                     listener = readyListeners.shift();
8054                     listener.fn.call(listener.scope);
8055
8056                     if (this.isLoading) {
8057                         return this;
8058                     }
8059                 }
8060             }
8061
8062             return this;
8063         },
8064
8065         /**
8066          * Adds new listener to be executed when all required scripts are fully loaded.
8067          *
8068          * @param {Function} fn The function callback to be executed
8069          * @param {Object} scope The execution scope (`this`) of the callback function
8070          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
8071          */
8072         onReady: function(fn, scope, withDomReady, options) {
8073             var oldFn;
8074
8075             if (withDomReady !== false && Ext.onDocumentReady) {
8076                 oldFn = fn;
8077
8078                 fn = function() {
8079                     Ext.onDocumentReady(oldFn, scope, options);
8080                 };
8081             }
8082
8083             if (!this.isLoading) {
8084                 fn.call(scope);
8085             }
8086             else {
8087                 this.readyListeners.push({
8088                     fn: fn,
8089                     scope: scope
8090                 });
8091             }
8092         },
8093
8094         /**
8095          * @private
8096          * @param {String} className
8097          */
8098         historyPush: function(className) {
8099             if (className && this.isFileLoaded.hasOwnProperty(className)) {
8100                 Ext.Array.include(this.history, className);
8101             }
8102
8103             return this;
8104         }
8105     };
8106
8107     /**
8108      * @member Ext
8109      * @method require
8110      * @alias Ext.Loader#require
8111      */
8112     Ext.require = alias(Loader, 'require');
8113
8114     /**
8115      * @member Ext
8116      * @method syncRequire
8117      * @alias Ext.Loader#syncRequire
8118      */
8119     Ext.syncRequire = alias(Loader, 'syncRequire');
8120
8121     /**
8122      * @member Ext
8123      * @method exclude
8124      * @alias Ext.Loader#exclude
8125      */
8126     Ext.exclude = alias(Loader, 'exclude');
8127
8128     /**
8129      * @member Ext
8130      * @method onReady
8131      * @alias Ext.Loader#onReady
8132      */
8133     Ext.onReady = function(fn, scope, options) {
8134         Loader.onReady(fn, scope, true, options);
8135     };
8136
8137     /**
8138      * @cfg {String[]} requires
8139      * @member Ext.Class
8140      * List of classes that have to be loaded before instantiating this class.
8141      * For example:
8142      *
8143      *     Ext.define('Mother', {
8144      *         requires: ['Child'],
8145      *         giveBirth: function() {
8146      *             // we can be sure that child class is available.
8147      *             return new Child();
8148      *         }
8149      *     });
8150      */
8151     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
8152         var me = this,
8153             dependencies = [],
8154             className = Manager.getName(cls),
8155             i, j, ln, subLn, value, propertyName, propertyValue;
8156
8157         /*
8158         Basically loop through the dependencyProperties, look for string class names and push
8159         them into a stack, regardless of whether the property's value is a string, array or object. For example:
8160         {
8161               extend: 'Ext.MyClass',
8162               requires: ['Ext.some.OtherClass'],
8163               mixins: {
8164                   observable: 'Ext.util.Observable';
8165               }
8166         }
8167         which will later be transformed into:
8168         {
8169               extend: Ext.MyClass,
8170               requires: [Ext.some.OtherClass],
8171               mixins: {
8172                   observable: Ext.util.Observable;
8173               }
8174         }
8175         */
8176
8177         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8178             propertyName = dependencyProperties[i];
8179
8180             if (data.hasOwnProperty(propertyName)) {
8181                 propertyValue = data[propertyName];
8182
8183                 if (typeof propertyValue === 'string') {
8184                     dependencies.push(propertyValue);
8185                 }
8186                 else if (propertyValue instanceof Array) {
8187                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8188                         value = propertyValue[j];
8189
8190                         if (typeof value === 'string') {
8191                             dependencies.push(value);
8192                         }
8193                     }
8194                 }
8195                 else if (typeof propertyValue != 'function') {
8196                     for (j in propertyValue) {
8197                         if (propertyValue.hasOwnProperty(j)) {
8198                             value = propertyValue[j];
8199
8200                             if (typeof value === 'string') {
8201                                 dependencies.push(value);
8202                             }
8203                         }
8204                     }
8205                 }
8206             }
8207         }
8208
8209         if (dependencies.length === 0) {
8210 //            Loader.historyPush(className);
8211             return;
8212         }
8213
8214
8215         Loader.require(dependencies, function() {
8216             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8217                 propertyName = dependencyProperties[i];
8218
8219                 if (data.hasOwnProperty(propertyName)) {
8220                     propertyValue = data[propertyName];
8221
8222                     if (typeof propertyValue === 'string') {
8223                         data[propertyName] = Manager.get(propertyValue);
8224                     }
8225                     else if (propertyValue instanceof Array) {
8226                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8227                             value = propertyValue[j];
8228
8229                             if (typeof value === 'string') {
8230                                 data[propertyName][j] = Manager.get(value);
8231                             }
8232                         }
8233                     }
8234                     else if (typeof propertyValue != 'function') {
8235                         for (var k in propertyValue) {
8236                             if (propertyValue.hasOwnProperty(k)) {
8237                                 value = propertyValue[k];
8238
8239                                 if (typeof value === 'string') {
8240                                     data[propertyName][k] = Manager.get(value);
8241                                 }
8242                             }
8243                         }
8244                     }
8245                 }
8246             }
8247
8248             continueFn.call(me, cls, data);
8249         });
8250
8251         return false;
8252     }, true);
8253
8254     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8255
8256     /**
8257      * @cfg {String[]} uses
8258      * @member Ext.Class
8259      * List of classes to load together with this class.  These aren't neccessarily loaded before
8260      * this class is instantiated. For example:
8261      *
8262      *     Ext.define('Mother', {
8263      *         uses: ['Child'],
8264      *         giveBirth: function() {
8265      *             // This code might, or might not work:
8266      *             // return new Child();
8267      *
8268      *             // Instead use Ext.create() to load the class at the spot if not loaded already:
8269      *             return Ext.create('Child');
8270      *         }
8271      *     });
8272      */
8273     Manager.registerPostprocessor('uses', function(name, cls, data) {
8274         var uses = Ext.Array.from(data.uses),
8275             items = [],
8276             i, ln, item;
8277
8278         for (i = 0, ln = uses.length; i < ln; i++) {
8279             item = uses[i];
8280
8281             if (typeof item === 'string') {
8282                 items.push(item);
8283             }
8284         }
8285
8286         Loader.addOptionalRequires(items);
8287     });
8288
8289     Manager.setDefaultPostprocessorPosition('uses', 'last');
8290
8291 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8292
8293 /**
8294  * @author Brian Moeskau <brian@sencha.com>
8295  * @docauthor Brian Moeskau <brian@sencha.com>
8296  *
8297  * A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8298  * errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8299  * uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8300  * the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8301  * with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8302  * execution will halt.
8303  *
8304  * Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8305  * handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8306  * although in a real application it's usually a better idea to override the handling function and perform
8307  * logging or some other method of reporting the errors in a way that is meaningful to the application.
8308  *
8309  * At its simplest you can simply raise an error as a simple string from within any code:
8310  *
8311  * Example usage:
8312  *
8313  *     Ext.Error.raise('Something bad happened!');
8314  *
8315  * If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8316  * displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8317  * additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
8318  * In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8319  * added to the error object and, if the console is available, logged to the console for inspection.
8320  *
8321  * Example usage:
8322  *
8323  *     Ext.define('Ext.Foo', {
8324  *         doSomething: function(option){
8325  *             if (someCondition === false) {
8326  *                 Ext.Error.raise({
8327  *                     msg: 'You cannot do that!',
8328  *                     option: option,   // whatever was passed into the method
8329  *                     'error code': 100 // other arbitrary info
8330  *                 });
8331  *             }
8332  *         }
8333  *     });
8334  *
8335  * If a console is available (that supports the `console.dir` function) you'll see console output like:
8336  *
8337  *     An error was raised with the following data:
8338  *     option:         Object { foo: "bar"}
8339  *         foo:        "bar"
8340  *     error code:     100
8341  *     msg:            "You cannot do that!"
8342  *     sourceClass:   "Ext.Foo"
8343  *     sourceMethod:  "doSomething"
8344  *
8345  *     uncaught exception: You cannot do that!
8346  *
8347  * As you can see, the error will report exactly where it was raised and will include as much information as the
8348  * raising code can usefully provide.
8349  *
8350  * If you want to handle all application errors globally you can simply override the static {@link #handle} method
8351  * and provide whatever handling logic you need. If the method returns true then the error is considered handled
8352  * and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8353  *
8354  * Example usage:
8355  *
8356  *     Ext.Error.handle = function(err) {
8357  *         if (err.someProperty == 'NotReallyAnError') {
8358  *             // maybe log something to the application here if applicable
8359  *             return true;
8360  *         }
8361  *         // any non-true return value (including none) will cause the error to be thrown
8362  *     }
8363  *
8364  */
8365 Ext.Error = Ext.extend(Error, {
8366     statics: {
8367         /**
8368          * @property {Boolean} ignore
8369          * Static flag that can be used to globally disable error reporting to the browser if set to true
8370          * (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8371          * and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8372          * be preferable to supply a custom error {@link #handle handling} function instead.
8373          *
8374          * Example usage:
8375          *
8376          *     Ext.Error.ignore = true;
8377          *
8378          * @static
8379          */
8380         ignore: false,
8381
8382         /**
8383          * @property {Boolean} notify
8384          * Static flag that can be used to globally control error notification to the user. Unlike
8385          * Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8386          * set to false to disable the alert notification (default is true for IE6 and IE7).
8387          *
8388          * Only the first error will generate an alert. Internally this flag is set to false when the
8389          * first error occurs prior to displaying the alert.
8390          *
8391          * This flag is not used in a release build.
8392          *
8393          * Example usage:
8394          *
8395          *     Ext.Error.notify = false;
8396          *
8397          * @static
8398          */
8399         //notify: Ext.isIE6 || Ext.isIE7,
8400
8401         /**
8402          * Raise an error that can include additional data and supports automatic console logging if available.
8403          * You can pass a string error message or an object with the `msg` attribute which will be used as the
8404          * error message. The object can contain any other name-value attributes (or objects) to be logged
8405          * along with the error.
8406          *
8407          * Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8408          * execution will halt.
8409          *
8410          * Example usage:
8411          *
8412          *     Ext.Error.raise('A simple string error message');
8413          *
8414          *     // or...
8415          *
8416          *     Ext.define('Ext.Foo', {
8417          *         doSomething: function(option){
8418          *             if (someCondition === false) {
8419          *                 Ext.Error.raise({
8420          *                     msg: 'You cannot do that!',
8421          *                     option: option,   // whatever was passed into the method
8422          *                     'error code': 100 // other arbitrary info
8423          *                 });
8424          *             }
8425          *         }
8426          *     });
8427          *
8428          * @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
8429          * used as the error message. Any other data included in the object will also be logged to the browser console,
8430          * if available.
8431          * @static
8432          */
8433         raise: function(err){
8434             err = err || {};
8435             if (Ext.isString(err)) {
8436                 err = { msg: err };
8437             }
8438
8439             var method = this.raise.caller;
8440
8441             if (method) {
8442                 if (method.$name) {
8443                     err.sourceMethod = method.$name;
8444                 }
8445                 if (method.$owner) {
8446                     err.sourceClass = method.$owner.$className;
8447                 }
8448             }
8449
8450             if (Ext.Error.handle(err) !== true) {
8451                 var msg = Ext.Error.prototype.toString.call(err);
8452
8453                 Ext.log({
8454                     msg: msg,
8455                     level: 'error',
8456                     dump: err,
8457                     stack: true
8458                 });
8459
8460                 throw new Ext.Error(err);
8461             }
8462         },
8463
8464         /**
8465          * Globally handle any Ext errors that may be raised, optionally providing custom logic to
8466          * handle different errors individually. Return true from the function to bypass throwing the
8467          * error to the browser, otherwise the error will be thrown and execution will halt.
8468          *
8469          * Example usage:
8470          *
8471          *     Ext.Error.handle = function(err) {
8472          *         if (err.someProperty == 'NotReallyAnError') {
8473          *             // maybe log something to the application here if applicable
8474          *             return true;
8475          *         }
8476          *         // any non-true return value (including none) will cause the error to be thrown
8477          *     }
8478          *
8479          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
8480          * raised with it, plus properties about the method and class from which the error originated (if raised from a
8481          * class that uses the Ext 4 class system).
8482          * @static
8483          */
8484         handle: function(){
8485             return Ext.Error.ignore;
8486         }
8487     },
8488
8489     // This is the standard property that is the name of the constructor.
8490     name: 'Ext.Error',
8491
8492     /**
8493      * Creates new Error object.
8494      * @param {String/Object} config The error message string, or an object containing the
8495      * attribute "msg" that will be used as the error message. Any other data included in
8496      * the object will be applied to the error instance and logged to the browser console, if available.
8497      */
8498     constructor: function(config){
8499         if (Ext.isString(config)) {
8500             config = { msg: config };
8501         }
8502
8503         var me = this;
8504
8505         Ext.apply(me, config);
8506
8507         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8508         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8509     },
8510
8511     /**
8512      * Provides a custom string representation of the error object. This is an override of the base JavaScript
8513      * `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8514      * be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8515      *
8516      * The default implementation will include the error message along with the raising class and method, if available,
8517      * but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8518      * a particular error instance, if you want to provide a custom description that will show up in the console.
8519      * @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
8520      * include the raising class and method names, if available.
8521      */
8522     toString: function(){
8523         var me = this,
8524             className = me.className ? me.className  : '',
8525             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8526             msg = me.msg || '(No description provided)';
8527
8528         return className + methodName + msg;
8529     }
8530 });
8531
8532 /*
8533  * This mechanism is used to notify the user of the first error encountered on the page. This
8534  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8535  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8536  * where exceptions are handled in a try/catch.
8537  */
8538
8539
8540
8541 /*
8542
8543 This file is part of Ext JS 4
8544
8545 Copyright (c) 2011 Sencha Inc
8546
8547 Contact:  http://www.sencha.com/contact
8548
8549 Commercial Usage
8550 Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
8551
8552 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
8553
8554 */
8555 /**
8556  * @class Ext.JSON
8557  * Modified version of Douglas Crockford's JSON.js that doesn't
8558  * mess with the Object prototype
8559  * http://www.json.org/js.html
8560  * @singleton
8561  */
8562 Ext.JSON = new(function() {
8563     var useHasOwn = !! {}.hasOwnProperty,
8564     isNative = function() {
8565         var useNative = null;
8566
8567         return function() {
8568             if (useNative === null) {
8569                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
8570             }
8571
8572             return useNative;
8573         };
8574     }(),
8575     pad = function(n) {
8576         return n < 10 ? "0" + n : n;
8577     },
8578     doDecode = function(json) {
8579         return eval("(" + json + ')');
8580     },
8581     doEncode = function(o) {
8582         if (!Ext.isDefined(o) || o === null) {
8583             return "null";
8584         } else if (Ext.isArray(o)) {
8585             return encodeArray(o);
8586         } else if (Ext.isDate(o)) {
8587             return Ext.JSON.encodeDate(o);
8588         } else if (Ext.isString(o)) {
8589             return encodeString(o);
8590         } else if (typeof o == "number") {
8591             //don't use isNumber here, since finite checks happen inside isNumber
8592             return isFinite(o) ? String(o) : "null";
8593         } else if (Ext.isBoolean(o)) {
8594             return String(o);
8595         } else if (Ext.isObject(o)) {
8596             return encodeObject(o);
8597         } else if (typeof o === "function") {
8598             return "null";
8599         }
8600         return 'undefined';
8601     },
8602     m = {
8603         "\b": '\\b',
8604         "\t": '\\t',
8605         "\n": '\\n',
8606         "\f": '\\f',
8607         "\r": '\\r',
8608         '"': '\\"',
8609         "\\": '\\\\',
8610         '\x0b': '\\u000b' //ie doesn't handle \v
8611     },
8612     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8613     encodeString = function(s) {
8614         return '"' + s.replace(charToReplace, function(a) {
8615             var c = m[a];
8616             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8617         }) + '"';
8618     },
8619     encodeArray = function(o) {
8620         var a = ["[", ""],
8621         // Note empty string in case there are no serializable members.
8622         len = o.length,
8623         i;
8624         for (i = 0; i < len; i += 1) {
8625             a.push(doEncode(o[i]), ',');
8626         }
8627         // Overwrite trailing comma (or empty string)
8628         a[a.length - 1] = ']';
8629         return a.join("");
8630     },
8631     encodeObject = function(o) {
8632         var a = ["{", ""],
8633         // Note empty string in case there are no serializable members.
8634         i;
8635         for (i in o) {
8636             if (!useHasOwn || o.hasOwnProperty(i)) {
8637                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8638             }
8639         }
8640         // Overwrite trailing comma (or empty string)
8641         a[a.length - 1] = '}';
8642         return a.join("");
8643     };
8644
8645     /**
8646      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8647      * <b>The returned value includes enclosing double quotation marks.</b></p>
8648      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8649      * <p>To override this:</p><pre><code>
8650 Ext.JSON.encodeDate = function(d) {
8651     return Ext.Date.format(d, '"Y-m-d"');
8652 };
8653      </code></pre>
8654      * @param {Date} d The Date to encode
8655      * @return {String} The string literal to use in a JSON string.
8656      */
8657     this.encodeDate = function(o) {
8658         return '"' + o.getFullYear() + "-"
8659         + pad(o.getMonth() + 1) + "-"
8660         + pad(o.getDate()) + "T"
8661         + pad(o.getHours()) + ":"
8662         + pad(o.getMinutes()) + ":"
8663         + pad(o.getSeconds()) + '"';
8664     };
8665
8666     /**
8667      * Encodes an Object, Array or other value
8668      * @param {Object} o The variable to encode
8669      * @return {String} The JSON string
8670      */
8671     this.encode = function() {
8672         var ec;
8673         return function(o) {
8674             if (!ec) {
8675                 // setup encoding function on first access
8676                 ec = isNative() ? JSON.stringify : doEncode;
8677             }
8678             return ec(o);
8679         };
8680     }();
8681
8682
8683     /**
8684      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
8685      * @param {String} json The JSON string
8686      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8687      * @return {Object} The resulting object
8688      */
8689     this.decode = function() {
8690         var dc;
8691         return function(json, safe) {
8692             if (!dc) {
8693                 // setup decoding function on first access
8694                 dc = isNative() ? JSON.parse : doDecode;
8695             }
8696             try {
8697                 return dc(json);
8698             } catch (e) {
8699                 if (safe === true) {
8700                     return null;
8701                 }
8702                 Ext.Error.raise({
8703                     sourceClass: "Ext.JSON",
8704                     sourceMethod: "decode",
8705                     msg: "You're trying to decode an invalid JSON String: " + json
8706                 });
8707             }
8708         };
8709     }();
8710
8711 })();
8712 /**
8713  * Shorthand for {@link Ext.JSON#encode}
8714  * @member Ext
8715  * @method encode
8716  * @alias Ext.JSON#encode
8717  */
8718 Ext.encode = Ext.JSON.encode;
8719 /**
8720  * Shorthand for {@link Ext.JSON#decode}
8721  * @member Ext
8722  * @method decode
8723  * @alias Ext.JSON#decode
8724  */
8725 Ext.decode = Ext.JSON.decode;
8726
8727
8728 /**
8729  * @class Ext
8730
8731  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
8732  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
8733  as direct properties of the Ext namespace.
8734
8735  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
8736  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
8737
8738  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
8739  This ensures all scripts have been loaded, preventing dependency issues. For example
8740
8741      Ext.onReady(function(){
8742          new Ext.Component({
8743              renderTo: document.body,
8744              html: 'DOM ready!'
8745          });
8746      });
8747
8748 For more information about how to use the Ext classes, see
8749
8750 - <a href="http://www.sencha.com/learn/">The Learning Center</a>
8751 - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
8752 - <a href="http://www.sencha.com/forum/">The forums</a>
8753
8754  * @singleton
8755  * @markdown
8756  */
8757 Ext.apply(Ext, {
8758     userAgent: navigator.userAgent.toLowerCase(),
8759     cache: {},
8760     idSeed: 1000,
8761     windowId: 'ext-window',
8762     documentId: 'ext-document',
8763
8764     /**
8765      * True when the document is fully initialized and ready for action
8766      * @type Boolean
8767      */
8768     isReady: false,
8769
8770     /**
8771      * True to automatically uncache orphaned Ext.Elements periodically
8772      * @type Boolean
8773      */
8774     enableGarbageCollector: true,
8775
8776     /**
8777      * True to automatically purge event listeners during garbageCollection.
8778      * @type Boolean
8779      */
8780     enableListenerCollection: true,
8781
8782     /**
8783      * Generates unique ids. If the element already has an id, it is unchanged
8784      * @param {HTMLElement/Ext.Element} el (optional) The element to generate an id for
8785      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
8786      * @return {String} The generated Id.
8787      */
8788     id: function(el, prefix) {
8789         var me = this,
8790             sandboxPrefix = '';
8791         el = Ext.getDom(el, true) || {};
8792         if (el === document) {
8793             el.id = me.documentId;
8794         }
8795         else if (el === window) {
8796             el.id = me.windowId;
8797         }
8798         if (!el.id) {
8799             if (me.isSandboxed) {
8800                 if (!me.uniqueGlobalNamespace) {
8801                     me.getUniqueGlobalNamespace();
8802                 }
8803                 sandboxPrefix = me.uniqueGlobalNamespace + '-';
8804             }
8805             el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed);
8806         }
8807         return el.id;
8808     },
8809
8810     /**
8811      * Returns the current document body as an {@link Ext.Element}.
8812      * @return Ext.Element The document body
8813      */
8814     getBody: function() {
8815         return Ext.get(document.body || false);
8816     },
8817
8818     /**
8819      * Returns the current document head as an {@link Ext.Element}.
8820      * @return Ext.Element The document head
8821      * @method
8822      */
8823     getHead: function() {
8824         var head;
8825
8826         return function() {
8827             if (head == undefined) {
8828                 head = Ext.get(document.getElementsByTagName("head")[0]);
8829             }
8830
8831             return head;
8832         };
8833     }(),
8834
8835     /**
8836      * Returns the current HTML document object as an {@link Ext.Element}.
8837      * @return Ext.Element The document
8838      */
8839     getDoc: function() {
8840         return Ext.get(document);
8841     },
8842
8843     /**
8844      * This is shorthand reference to {@link Ext.ComponentManager#get}.
8845      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
8846      * @param {String} id The component {@link Ext.Component#id id}
8847      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
8848      * Class was found.
8849     */
8850     getCmp: function(id) {
8851         return Ext.ComponentManager.get(id);
8852     },
8853
8854     /**
8855      * Returns the current orientation of the mobile device
8856      * @return {String} Either 'portrait' or 'landscape'
8857      */
8858     getOrientation: function() {
8859         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
8860     },
8861
8862     /**
8863      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
8864      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
8865      * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of
8866      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
8867      * passed into this function in a single call as separate arguments.
8868      * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} arg1
8869      * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
8870      */
8871     destroy: function() {
8872         var ln = arguments.length,
8873         i, arg;
8874
8875         for (i = 0; i < ln; i++) {
8876             arg = arguments[i];
8877             if (arg) {
8878                 if (Ext.isArray(arg)) {
8879                     this.destroy.apply(this, arg);
8880                 }
8881                 else if (Ext.isFunction(arg.destroy)) {
8882                     arg.destroy();
8883                 }
8884                 else if (arg.dom) {
8885                     arg.remove();
8886                 }
8887             }
8888         }
8889     },
8890
8891     /**
8892      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
8893      *
8894      * For example, these lines are equivalent:
8895      *
8896      *     Ext.callback(myFunc, this, [arg1, arg2]);
8897      *     Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]);
8898      *
8899      * @param {Function} callback The callback to execute
8900      * @param {Object} scope (optional) The scope to execute in
8901      * @param {Array} args (optional) The arguments to pass to the function
8902      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
8903      */
8904     callback: function(callback, scope, args, delay){
8905         if(Ext.isFunction(callback)){
8906             args = args || [];
8907             scope = scope || window;
8908             if (delay) {
8909                 Ext.defer(callback, delay, scope, args);
8910             } else {
8911                 callback.apply(scope, args);
8912             }
8913         }
8914     },
8915
8916     /**
8917      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
8918      * @param {String} value The string to encode
8919      * @return {String} The encoded text
8920      */
8921     htmlEncode : function(value) {
8922         return Ext.String.htmlEncode(value);
8923     },
8924
8925     /**
8926      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
8927      * @param {String} value The string to decode
8928      * @return {String} The decoded text
8929      */
8930     htmlDecode : function(value) {
8931          return Ext.String.htmlDecode(value);
8932     },
8933
8934     /**
8935      * Appends content to the query string of a URL, handling logic for whether to place
8936      * a question mark or ampersand.
8937      * @param {String} url The URL to append to.
8938      * @param {String} s The content to append to the URL.
8939      * @return (String) The resulting URL
8940      */
8941     urlAppend : function(url, s) {
8942         if (!Ext.isEmpty(s)) {
8943             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
8944         }
8945         return url;
8946     }
8947 });
8948
8949
8950 Ext.ns = Ext.namespace;
8951
8952 // for old browsers
8953 window.undefined = window.undefined;
8954
8955 /**
8956  * @class Ext
8957  * Ext core utilities and functions.
8958  * @singleton
8959  */
8960 (function(){
8961 /*
8962 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
8963 FF 4.0.1    - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
8964 FF 5.0      - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0
8965
8966 IE6         - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)
8967 IE7         - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;)
8968 IE8         - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
8969 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)
8970
8971 Chrome 11   - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24
8972
8973 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
8974
8975 Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11
8976 */
8977     var check = function(regex){
8978             return regex.test(Ext.userAgent);
8979         },
8980         isStrict = document.compatMode == "CSS1Compat",
8981         version = function (is, regex) {
8982             var m;
8983             return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0;
8984         },
8985         docMode = document.documentMode,
8986         isOpera = check(/opera/),
8987         isOpera10_5 = isOpera && check(/version\/10\.5/),
8988         isChrome = check(/\bchrome\b/),
8989         isWebKit = check(/webkit/),
8990         isSafari = !isChrome && check(/safari/),
8991         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
8992         isSafari3 = isSafari && check(/version\/3/),
8993         isSafari4 = isSafari && check(/version\/4/),
8994         isSafari5 = isSafari && check(/version\/5/),
8995         isIE = !isOpera && check(/msie/),
8996         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
8997         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
8998         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
8999         isIE6 = isIE && check(/msie 6/),
9000         isGecko = !isWebKit && check(/gecko/),
9001         isGecko3 = isGecko && check(/rv:1\.9/),
9002         isGecko4 = isGecko && check(/rv:2\.0/),
9003         isGecko5 = isGecko && check(/rv:5\./),
9004         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
9005         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
9006         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
9007         isWindows = check(/windows|win32/),
9008         isMac = check(/macintosh|mac os x/),
9009         isLinux = check(/linux/),
9010         scrollbarSize = null,
9011         chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/),
9012         firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/),
9013         ieVersion = version(isIE, /msie (\d+\.\d+)/),
9014         operaVersion = version(isOpera, /version\/(\d+\.\d+)/),
9015         safariVersion = version(isSafari, /version\/(\d+\.\d+)/),
9016         webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/),
9017         isSecure = /^https/i.test(window.location.protocol);
9018
9019     // remove css image flicker
9020     try {
9021         document.execCommand("BackgroundImageCache", false, true);
9022     } catch(e) {}
9023
9024
9025     Ext.setVersion('extjs', '4.0.7');
9026     Ext.apply(Ext, {
9027         /**
9028          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
9029          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
9030          * @type String
9031          */
9032         SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
9033
9034         /**
9035          * True if the {@link Ext.fx.Anim} Class is available
9036          * @type Boolean
9037          * @property enableFx
9038          */
9039
9040         /**
9041          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
9042          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
9043          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
9044          * @type Boolean
9045          */
9046         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
9047
9048         /**
9049          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
9050          * Currently not optimized for performance.
9051          * @type Boolean
9052          */
9053         enableNestedListenerRemoval : false,
9054
9055         /**
9056          * Indicates whether to use native browser parsing for JSON methods.
9057          * This option is ignored if the browser does not support native JSON methods.
9058          * <b>Note: Native JSON methods will not work with objects that have functions.
9059          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
9060          * @type Boolean
9061          */
9062         USE_NATIVE_JSON : false,
9063
9064         /**
9065          * Return the dom node for the passed String (id), dom node, or Ext.Element.
9066          * Optional 'strict' flag is needed for IE since it can return 'name' and
9067          * 'id' elements by using getElementById.
9068          * Here are some examples:
9069          * <pre><code>
9070 // gets dom node based on id
9071 var elDom = Ext.getDom('elId');
9072 // gets dom node based on the dom node
9073 var elDom1 = Ext.getDom(elDom);
9074
9075 // If we don&#39;t know if we are working with an
9076 // Ext.Element or a dom node use Ext.getDom
9077 function(el){
9078     var dom = Ext.getDom(el);
9079     // do something with the dom node
9080 }
9081          * </code></pre>
9082          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
9083          * when this method is called to be successful.
9084          * @param {String/HTMLElement/Ext.Element} el
9085          * @return HTMLElement
9086          */
9087         getDom : function(el, strict) {
9088             if (!el || !document) {
9089                 return null;
9090             }
9091             if (el.dom) {
9092                 return el.dom;
9093             } else {
9094                 if (typeof el == 'string') {
9095                     var e = document.getElementById(el);
9096                     // IE returns elements with the 'name' and 'id' attribute.
9097                     // we do a strict check to return the element with only the id attribute
9098                     if (e && isIE && strict) {
9099                         if (el == e.getAttribute('id')) {
9100                             return e;
9101                         } else {
9102                             return null;
9103                         }
9104                     }
9105                     return e;
9106                 } else {
9107                     return el;
9108                 }
9109             }
9110         },
9111
9112         /**
9113          * Removes a DOM node from the document.
9114          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
9115          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
9116          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
9117          * will be ignored if passed in.</p>
9118          * @param {HTMLElement} node The node to remove
9119          * @method
9120          */
9121         removeNode : isIE6 || isIE7 ? function() {
9122             var d;
9123             return function(n){
9124                 if(n && n.tagName != 'BODY'){
9125                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9126                     d = d || document.createElement('div');
9127                     d.appendChild(n);
9128                     d.innerHTML = '';
9129                     delete Ext.cache[n.id];
9130                 }
9131             };
9132         }() : function(n) {
9133             if (n && n.parentNode && n.tagName != 'BODY') {
9134                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
9135                 n.parentNode.removeChild(n);
9136                 delete Ext.cache[n.id];
9137             }
9138         },
9139
9140         isStrict: isStrict,
9141
9142         isIEQuirks: isIE && !isStrict,
9143
9144         /**
9145          * True if the detected browser is Opera.
9146          * @type Boolean
9147          */
9148         isOpera : isOpera,
9149
9150         /**
9151          * True if the detected browser is Opera 10.5x.
9152          * @type Boolean
9153          */
9154         isOpera10_5 : isOpera10_5,
9155
9156         /**
9157          * True if the detected browser uses WebKit.
9158          * @type Boolean
9159          */
9160         isWebKit : isWebKit,
9161
9162         /**
9163          * True if the detected browser is Chrome.
9164          * @type Boolean
9165          */
9166         isChrome : isChrome,
9167
9168         /**
9169          * True if the detected browser is Safari.
9170          * @type Boolean
9171          */
9172         isSafari : isSafari,
9173
9174         /**
9175          * True if the detected browser is Safari 3.x.
9176          * @type Boolean
9177          */
9178         isSafari3 : isSafari3,
9179
9180         /**
9181          * True if the detected browser is Safari 4.x.
9182          * @type Boolean
9183          */
9184         isSafari4 : isSafari4,
9185
9186         /**
9187          * True if the detected browser is Safari 5.x.
9188          * @type Boolean
9189          */
9190         isSafari5 : isSafari5,
9191
9192         /**
9193          * True if the detected browser is Safari 2.x.
9194          * @type Boolean
9195          */
9196         isSafari2 : isSafari2,
9197
9198         /**
9199          * True if the detected browser is Internet Explorer.
9200          * @type Boolean
9201          */
9202         isIE : isIE,
9203
9204         /**
9205          * True if the detected browser is Internet Explorer 6.x.
9206          * @type Boolean
9207          */
9208         isIE6 : isIE6,
9209
9210         /**
9211          * True if the detected browser is Internet Explorer 7.x.
9212          * @type Boolean
9213          */
9214         isIE7 : isIE7,
9215
9216         /**
9217          * True if the detected browser is Internet Explorer 8.x.
9218          * @type Boolean
9219          */
9220         isIE8 : isIE8,
9221
9222         /**
9223          * True if the detected browser is Internet Explorer 9.x.
9224          * @type Boolean
9225          */
9226         isIE9 : isIE9,
9227
9228         /**
9229          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
9230          * @type Boolean
9231          */
9232         isGecko : isGecko,
9233
9234         /**
9235          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
9236          * @type Boolean
9237          */
9238         isGecko3 : isGecko3,
9239
9240         /**
9241          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
9242          * @type Boolean
9243          */
9244         isGecko4 : isGecko4,
9245
9246         /**
9247          * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
9248          * @type Boolean
9249          */
9250         isGecko5 : isGecko5,
9251
9252         /**
9253          * True if the detected browser uses FireFox 3.0
9254          * @type Boolean
9255          */
9256         isFF3_0 : isFF3_0,
9257
9258         /**
9259          * True if the detected browser uses FireFox 3.5
9260          * @type Boolean
9261          */
9262         isFF3_5 : isFF3_5,
9263
9264         /**
9265          * True if the detected browser uses FireFox 3.6
9266          * @type Boolean
9267          */
9268         isFF3_6 : isFF3_6,
9269
9270         /**
9271          * True if the detected browser uses FireFox 4
9272          * @type Boolean
9273          */
9274         isFF4 : 4 <= firefoxVersion && firefoxVersion < 5,
9275
9276         /**
9277          * True if the detected browser uses FireFox 5
9278          * @type Boolean
9279          */
9280         isFF5 : 5 <= firefoxVersion && firefoxVersion < 6,
9281
9282         /**
9283          * True if the detected platform is Linux.
9284          * @type Boolean
9285          */
9286         isLinux : isLinux,
9287
9288         /**
9289          * True if the detected platform is Windows.
9290          * @type Boolean
9291          */
9292         isWindows : isWindows,
9293
9294         /**
9295          * True if the detected platform is Mac OS.
9296          * @type Boolean
9297          */
9298         isMac : isMac,
9299
9300         /**
9301          * The current version of Chrome (0 if the browser is not Chrome).
9302          * @type Number
9303          */
9304         chromeVersion: chromeVersion,
9305
9306         /**
9307          * The current version of Firefox (0 if the browser is not Firefox).
9308          * @type Number
9309          */
9310         firefoxVersion: firefoxVersion,
9311
9312         /**
9313          * The current version of IE (0 if the browser is not IE). This does not account
9314          * for the documentMode of the current page, which is factored into {@link #isIE7},
9315          * {@link #isIE8} and {@link #isIE9}. Thus this is not always true:
9316          *
9317          *      Ext.isIE8 == (Ext.ieVersion == 8)
9318          *
9319          * @type Number
9320          * @markdown
9321          */
9322         ieVersion: ieVersion,
9323
9324         /**
9325          * The current version of Opera (0 if the browser is not Opera).
9326          * @type Number
9327          */
9328         operaVersion: operaVersion,
9329
9330         /**
9331          * The current version of Safari (0 if the browser is not Safari).
9332          * @type Number
9333          */
9334         safariVersion: safariVersion,
9335
9336         /**
9337          * The current version of WebKit (0 if the browser does not use WebKit).
9338          * @type Number
9339          */
9340         webKitVersion: webKitVersion,
9341
9342         /**
9343          * True if the page is running over SSL
9344          * @type Boolean
9345          */
9346         isSecure: isSecure,
9347
9348         /**
9349          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
9350          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
9351          * For other browsers it uses an inline data URL.
9352          * @type String
9353          */
9354         BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
9355
9356         /**
9357          * <p>Utility method for returning a default value if the passed value is empty.</p>
9358          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
9359          * <li>null</li>
9360          * <li>undefined</li>
9361          * <li>an empty array</li>
9362          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
9363          * </ul></div>
9364          * @param {Object} value The value to test
9365          * @param {Object} defaultValue The value to return if the original value is empty
9366          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
9367          * @return {Object} value, if non-empty, else defaultValue
9368          * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead
9369          */
9370         value : function(v, defaultValue, allowBlank){
9371             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
9372         },
9373
9374         /**
9375          * Escapes the passed string for use in a regular expression
9376          * @param {String} str
9377          * @return {String}
9378          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
9379          */
9380         escapeRe : function(s) {
9381             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
9382         },
9383
9384         /**
9385          * Applies event listeners to elements by selectors when the document is ready.
9386          * The event name is specified with an <tt>&#64;</tt> suffix.
9387          * <pre><code>
9388 Ext.addBehaviors({
9389     // add a listener for click on all anchors in element with id foo
9390     '#foo a&#64;click' : function(e, t){
9391         // do something
9392     },
9393
9394     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
9395     '#foo a, #bar span.some-class&#64;mouseover' : function(){
9396         // do something
9397     }
9398 });
9399          * </code></pre>
9400          * @param {Object} obj The list of behaviors to apply
9401          */
9402         addBehaviors : function(o){
9403             if(!Ext.isReady){
9404                 Ext.onReady(function(){
9405                     Ext.addBehaviors(o);
9406                 });
9407             } else {
9408                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
9409                     parts,
9410                     b,
9411                     s;
9412                 for (b in o) {
9413                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
9414                         s = parts[0];
9415                         if(!cache[s]){
9416                             cache[s] = Ext.select(s);
9417                         }
9418                         cache[s].on(parts[1], o[b]);
9419                     }
9420                 }
9421                 cache = null;
9422             }
9423         },
9424
9425         /**
9426          * Returns the size of the browser scrollbars. This can differ depending on
9427          * operating system settings, such as the theme or font size.
9428          * @param {Boolean} force (optional) true to force a recalculation of the value.
9429          * @return {Object} An object containing the width of a vertical scrollbar and the
9430          * height of a horizontal scrollbar.
9431          */
9432         getScrollbarSize: function (force) {
9433             if(!Ext.isReady){
9434                 return 0;
9435             }
9436
9437             if(force === true || scrollbarSize === null){
9438                 // BrowserBug: IE9
9439                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
9440                 // inaccurately reported. For IE9 only, we render on screen before removing.
9441                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets',
9442                     // Append our div, do our calculation and then remove it
9443                     div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
9444                     child = div.child('div', true),
9445                     w1 = child.offsetWidth;
9446
9447                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
9448
9449                 var w2 = child.offsetWidth, width = w1 - w2;
9450                 div.remove();
9451
9452                 // We assume width == height for now. TODO: is this always true?
9453                 scrollbarSize = { width: width, height: width };
9454             }
9455
9456             return scrollbarSize;
9457         },
9458
9459         /**
9460          * Utility method for getting the width of the browser's vertical scrollbar. This
9461          * can differ depending on operating system settings, such as the theme or font size.
9462          *
9463          * This method is deprected in favor of {@link #getScrollbarSize}.
9464          *
9465          * @param {Boolean} force (optional) true to force a recalculation of the value.
9466          * @return {Number} The width of a vertical scrollbar.
9467          * @deprecated
9468          */
9469         getScrollBarWidth: function(force){
9470             var size = Ext.getScrollbarSize(force);
9471             return size.width + 2; // legacy fudge factor
9472         },
9473
9474         /**
9475          * Copies a set of named properties fom the source object to the destination object.
9476          *
9477          * Example:
9478          *
9479          *     ImageComponent = Ext.extend(Ext.Component, {
9480          *         initComponent: function() {
9481          *             this.autoEl = { tag: 'img' };
9482          *             MyComponent.superclass.initComponent.apply(this, arguments);
9483          *             this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
9484          *         }
9485          *     });
9486          *
9487          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
9488          *
9489          * @param {Object} dest The destination object.
9490          * @param {Object} source The source object.
9491          * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
9492          * of property names to copy.
9493          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
9494          * @return {Object} The modified object.
9495          */
9496         copyTo : function(dest, source, names, usePrototypeKeys){
9497             if(typeof names == 'string'){
9498                 names = names.split(/[,;\s]/);
9499             }
9500             Ext.each(names, function(name){
9501                 if(usePrototypeKeys || source.hasOwnProperty(name)){
9502                     dest[name] = source[name];
9503                 }
9504             }, this);
9505             return dest;
9506         },
9507
9508         /**
9509          * Attempts to destroy and then remove a set of named properties of the passed object.
9510          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
9511          * @param {String...} args One or more names of the properties to destroy and remove from the object.
9512          */
9513         destroyMembers : function(o){
9514             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
9515                 Ext.destroy(o[a[i]]);
9516                 delete o[a[i]];
9517             }
9518         },
9519
9520         /**
9521          * Logs a message. If a console is present it will be used. On Opera, the method
9522          * "opera.postError" is called. In other cases, the message is logged to an array
9523          * "Ext.log.out". An attached debugger can watch this array and view the log. The
9524          * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
9525          * The `Ext.log.out` array can also be written to a popup window by entering the
9526          * following in the URL bar (a "bookmarklet"):
9527          *
9528          *    javascript:void(Ext.log.show());
9529          *
9530          * If additional parameters are passed, they are joined and appended to the message.
9531          * A technique for tracing entry and exit of a function is this:
9532          *
9533          *      function foo () {
9534          *          Ext.log({ indent: 1 }, '>> foo');
9535          *
9536          *          // log statements in here or methods called from here will be indented
9537          *          // by one step
9538          *
9539          *          Ext.log({ outdent: 1 }, '<< foo');
9540          *      }
9541          *
9542          * This method does nothing in a release build.
9543          *
9544          * @param {String/Object} message The message to log or an options object with any
9545          * of the following properties:
9546          *
9547          *  - `msg`: The message to log (required).
9548          *  - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
9549          *  - `dump`: An object to dump to the log as part of the message.
9550          *  - `stack`: True to include a stack trace in the log.
9551          *  - `indent`: Cause subsequent log statements to be indented one step.
9552          *  - `outdent`: Cause this and following statements to be one step less indented.
9553          * @markdown
9554          */
9555         log :
9556             Ext.emptyFn,
9557
9558         /**
9559          * Partitions the set into two sets: a true set and a false set.
9560          * Example:
9561          * Example2:
9562          * <pre><code>
9563 // Example 1:
9564 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
9565
9566 // Example 2:
9567 Ext.partition(
9568     Ext.query("p"),
9569     function(val){
9570         return val.className == "class1"
9571     }
9572 );
9573 // true are those paragraph elements with a className of "class1",
9574 // false set are those that do not have that className.
9575          * </code></pre>
9576          * @param {Array/NodeList} arr The array to partition
9577          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
9578          * itself must be able to be evaluated for its truthfulness.
9579          * @return {Array} [array of truish values, array of falsy values]
9580          * @deprecated 4.0.0 Will be removed in the next major version
9581          */
9582         partition : function(arr, truth){
9583             var ret = [[],[]];
9584             Ext.each(arr, function(v, i, a) {
9585                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
9586             });
9587             return ret;
9588         },
9589
9590         /**
9591          * Invokes a method on each item in an Array.
9592          * <pre><code>
9593 // Example:
9594 Ext.invoke(Ext.query("p"), "getAttribute", "id");
9595 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
9596          * </code></pre>
9597          * @param {Array/NodeList} arr The Array of items to invoke the method on.
9598          * @param {String} methodName The method name to invoke.
9599          * @param {Object...} args Arguments to send into the method invocation.
9600          * @return {Array} The results of invoking the method on each item in the array.
9601          * @deprecated 4.0.0 Will be removed in the next major version
9602          */
9603         invoke : function(arr, methodName){
9604             var ret = [],
9605                 args = Array.prototype.slice.call(arguments, 2);
9606             Ext.each(arr, function(v,i) {
9607                 if (v && typeof v[methodName] == 'function') {
9608                     ret.push(v[methodName].apply(v, args));
9609                 } else {
9610                     ret.push(undefined);
9611                 }
9612             });
9613             return ret;
9614         },
9615
9616         /**
9617          * <p>Zips N sets together.</p>
9618          * <pre><code>
9619 // Example 1:
9620 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
9621 // Example 2:
9622 Ext.zip(
9623     [ "+", "-", "+"],
9624     [  12,  10,  22],
9625     [  43,  15,  96],
9626     function(a, b, c){
9627         return "$" + a + "" + b + "." + c
9628     }
9629 ); // ["$+12.43", "$-10.15", "$+22.96"]
9630          * </code></pre>
9631          * @param {Array/NodeList...} arr This argument may be repeated. Array(s) to contribute values.
9632          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
9633          * @return {Array} The zipped set.
9634          * @deprecated 4.0.0 Will be removed in the next major version
9635          */
9636         zip : function(){
9637             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
9638                 arrs = parts[0],
9639                 fn = parts[1][0],
9640                 len = Ext.max(Ext.pluck(arrs, "length")),
9641                 ret = [];
9642
9643             for (var i = 0; i < len; i++) {
9644                 ret[i] = [];
9645                 if(fn){
9646                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
9647                 }else{
9648                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
9649                         ret[i].push( arrs[j][i] );
9650                     }
9651                 }
9652             }
9653             return ret;
9654         },
9655
9656         /**
9657          * Turns an array into a sentence, joined by a specified connector - e.g.:
9658          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
9659          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
9660          * @param {String[]} items The array to create a sentence from
9661          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
9662          * @return {String} The sentence string
9663          * @deprecated 4.0.0 Will be removed in the next major version
9664          */
9665         toSentence: function(items, connector) {
9666             var length = items.length;
9667
9668             if (length <= 1) {
9669                 return items[0];
9670             } else {
9671                 var head = items.slice(0, length - 1),
9672                     tail = items[length - 1];
9673
9674                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
9675             }
9676         },
9677
9678         /**
9679          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
9680          * you may want to set this to true.
9681          * @type Boolean
9682          */
9683         useShims: isIE6
9684     });
9685 })();
9686
9687 /**
9688  * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
9689  *
9690  * See Ext.app.Application for details.
9691  *
9692  * @param {Object} config
9693  */
9694 Ext.application = function(config) {
9695     Ext.require('Ext.app.Application');
9696
9697     Ext.onReady(function() {
9698         Ext.create('Ext.app.Application', config);
9699     });
9700 };
9701
9702 /**
9703  * @class Ext.util.Format
9704
9705 This class is a centralized place for formatting functions. It includes
9706 functions to format various different types of data, such as text, dates and numeric values.
9707
9708 __Localization__
9709 This class contains several options for localization. These can be set once the library has loaded,
9710 all calls to the functions from that point will use the locale settings that were specified.
9711 Options include:
9712 - thousandSeparator
9713 - decimalSeparator
9714 - currenyPrecision
9715 - currencySign
9716 - currencyAtEnd
9717 This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
9718
9719 __Using with renderers__
9720 There are two helper functions that return a new function that can be used in conjunction with
9721 grid renderers:
9722
9723     columns: [{
9724         dataIndex: 'date',
9725         renderer: Ext.util.Format.dateRenderer('Y-m-d')
9726     }, {
9727         dataIndex: 'time',
9728         renderer: Ext.util.Format.numberRenderer('0.000')
9729     }]
9730
9731 Functions that only take a single argument can also be passed directly:
9732     columns: [{
9733         dataIndex: 'cost',
9734         renderer: Ext.util.Format.usMoney
9735     }, {
9736         dataIndex: 'productCode',
9737         renderer: Ext.util.Format.uppercase
9738     }]
9739
9740 __Using with XTemplates__
9741 XTemplates can also directly use Ext.util.Format functions:
9742
9743     new Ext.XTemplate([
9744         'Date: {startDate:date("Y-m-d")}',
9745         'Cost: {cost:usMoney}'
9746     ]);
9747
9748  * @markdown
9749  * @singleton
9750  */
9751 (function() {
9752     Ext.ns('Ext.util');
9753
9754     Ext.util.Format = {};
9755     var UtilFormat     = Ext.util.Format,
9756         stripTagsRE    = /<\/?[^>]+>/gi,
9757         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
9758         nl2brRe        = /\r?\n/g,
9759
9760         // A RegExp to remove from a number format string, all characters except digits and '.'
9761         formatCleanRe  = /[^\d\.]/g,
9762
9763         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
9764         // Created on first use. The local decimal separator character must be initialized for this to be created.
9765         I18NFormatCleanRe;
9766
9767     Ext.apply(UtilFormat, {
9768         /**
9769          * @property {String} thousandSeparator
9770          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
9771          * <p>This may be overridden in a locale file.</p>
9772          */
9773         thousandSeparator: ',',
9774
9775         /**
9776          * @property {String} decimalSeparator
9777          * <p>The character that the {@link #number} function uses as a decimal point.</p>
9778          * <p>This may be overridden in a locale file.</p>
9779          */
9780         decimalSeparator: '.',
9781
9782         /**
9783          * @property {Number} currencyPrecision
9784          * <p>The number of decimal places that the {@link #currency} function displays.</p>
9785          * <p>This may be overridden in a locale file.</p>
9786          */
9787         currencyPrecision: 2,
9788
9789         /**
9790          * @property {String} currencySign
9791          * <p>The currency sign that the {@link #currency} function displays.</p>
9792          * <p>This may be overridden in a locale file.</p>
9793          */
9794         currencySign: '$',
9795
9796         /**
9797          * @property {Boolean} currencyAtEnd
9798          * <p>This may be set to <code>true</code> to make the {@link #currency} function
9799          * append the currency sign to the formatted value.</p>
9800          * <p>This may be overridden in a locale file.</p>
9801          */
9802         currencyAtEnd: false,
9803
9804         /**
9805          * Checks a reference and converts it to empty string if it is undefined
9806          * @param {Object} value Reference to check
9807          * @return {Object} Empty string if converted, otherwise the original value
9808          */
9809         undef : function(value) {
9810             return value !== undefined ? value : "";
9811         },
9812
9813         /**
9814          * Checks a reference and converts it to the default value if it's empty
9815          * @param {Object} value Reference to check
9816          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
9817          * @return {String}
9818          */
9819         defaultValue : function(value, defaultValue) {
9820             return value !== undefined && value !== '' ? value : defaultValue;
9821         },
9822
9823         /**
9824          * Returns a substring from within an original string
9825          * @param {String} value The original text
9826          * @param {Number} start The start index of the substring
9827          * @param {Number} length The length of the substring
9828          * @return {String} The substring
9829          */
9830         substr : function(value, start, length) {
9831             return String(value).substr(start, length);
9832         },
9833
9834         /**
9835          * Converts a string to all lower case letters
9836          * @param {String} value The text to convert
9837          * @return {String} The converted text
9838          */
9839         lowercase : function(value) {
9840             return String(value).toLowerCase();
9841         },
9842
9843         /**
9844          * Converts a string to all upper case letters
9845          * @param {String} value The text to convert
9846          * @return {String} The converted text
9847          */
9848         uppercase : function(value) {
9849             return String(value).toUpperCase();
9850         },
9851
9852         /**
9853          * Format a number as US currency
9854          * @param {Number/String} value The numeric value to format
9855          * @return {String} The formatted currency string
9856          */
9857         usMoney : function(v) {
9858             return UtilFormat.currency(v, '$', 2);
9859         },
9860
9861         /**
9862          * Format a number as a currency
9863          * @param {Number/String} value The numeric value to format
9864          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
9865          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
9866          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
9867          * @return {String} The formatted currency string
9868          */
9869         currency: function(v, currencySign, decimals, end) {
9870             var negativeSign = '',
9871                 format = ",0",
9872                 i = 0;
9873             v = v - 0;
9874             if (v < 0) {
9875                 v = -v;
9876                 negativeSign = '-';
9877             }
9878             decimals = decimals || UtilFormat.currencyPrecision;
9879             format += format + (decimals > 0 ? '.' : '');
9880             for (; i < decimals; i++) {
9881                 format += '0';
9882             }
9883             v = UtilFormat.number(v, format);
9884             if ((end || UtilFormat.currencyAtEnd) === true) {
9885                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
9886             } else {
9887                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
9888             }
9889         },
9890
9891         /**
9892          * Formats the passed date using the specified format pattern.
9893          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
9894          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
9895          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9896          * @return {String} The formatted date string.
9897          */
9898         date: function(v, format) {
9899             if (!v) {
9900                 return "";
9901             }
9902             if (!Ext.isDate(v)) {
9903                 v = new Date(Date.parse(v));
9904             }
9905             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
9906         },
9907
9908         /**
9909          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
9910          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9911          * @return {Function} The date formatting function
9912          */
9913         dateRenderer : function(format) {
9914             return function(v) {
9915                 return UtilFormat.date(v, format);
9916             };
9917         },
9918
9919         /**
9920          * Strips all HTML tags
9921          * @param {Object} value The text from which to strip tags
9922          * @return {String} The stripped text
9923          */
9924         stripTags : function(v) {
9925             return !v ? v : String(v).replace(stripTagsRE, "");
9926         },
9927
9928         /**
9929          * Strips all script tags
9930          * @param {Object} value The text from which to strip script tags
9931          * @return {String} The stripped text
9932          */
9933         stripScripts : function(v) {
9934             return !v ? v : String(v).replace(stripScriptsRe, "");
9935         },
9936
9937         /**
9938          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
9939          * @param {Number/String} size The numeric value to format
9940          * @return {String} The formatted file size
9941          */
9942         fileSize : function(size) {
9943             if (size < 1024) {
9944                 return size + " bytes";
9945             } else if (size < 1048576) {
9946                 return (Math.round(((size*10) / 1024))/10) + " KB";
9947             } else {
9948                 return (Math.round(((size*10) / 1048576))/10) + " MB";
9949             }
9950         },
9951
9952         /**
9953          * It does simple math for use in a template, for example:<pre><code>
9954          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
9955          * </code></pre>
9956          * @return {Function} A function that operates on the passed value.
9957          * @method
9958          */
9959         math : function(){
9960             var fns = {};
9961
9962             return function(v, a){
9963                 if (!fns[a]) {
9964                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
9965                 }
9966                 return fns[a](v);
9967             };
9968         }(),
9969
9970         /**
9971          * Rounds the passed number to the required decimal precision.
9972          * @param {Number/String} value The numeric value to round.
9973          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
9974          * @return {Number} The rounded value.
9975          */
9976         round : function(value, precision) {
9977             var result = Number(value);
9978             if (typeof precision == 'number') {
9979                 precision = Math.pow(10, precision);
9980                 result = Math.round(value * precision) / precision;
9981             }
9982             return result;
9983         },
9984
9985         /**
9986          * <p>Formats the passed number according to the passed format string.</p>
9987          * <p>The number of digits after the decimal separator character specifies the number of
9988          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
9989          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
9990          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
9991          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
9992          * <p><b>New to Ext JS 4</b></p>
9993          * <p>Locale-specific characters are always used in the formatted output when inserting
9994          * thousand and decimal separators.</p>
9995          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
9996          * thousand separator, and "." as the decimal separator)</p>
9997          * <p>To allow specification of format strings according to local conventions for separator characters, add
9998          * the string <code>/i</code> to the end of the format string.</p>
9999          * <div style="margin-left:40px">examples (123456.789):
10000          * <div style="margin-left:10px">
10001          * 0 - (123456) show only digits, no precision<br>
10002          * 0.00 - (123456.78) show only digits, 2 precision<br>
10003          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
10004          * 0,000 - (123,456) show comma and digits, no precision<br>
10005          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
10006          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
10007          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
10008          * For example: 0.000,00/i
10009          * </div></div>
10010          * @param {Number} v The number to format.
10011          * @param {String} format The way you would like to format this text.
10012          * @return {String} The formatted number.
10013          */
10014         number: function(v, formatString) {
10015             if (!formatString) {
10016                 return v;
10017             }
10018             v = Ext.Number.from(v, NaN);
10019             if (isNaN(v)) {
10020                 return '';
10021             }
10022             var comma = UtilFormat.thousandSeparator,
10023                 dec   = UtilFormat.decimalSeparator,
10024                 i18n  = false,
10025                 neg   = v < 0,
10026                 hasComma,
10027                 psplit;
10028
10029             v = Math.abs(v);
10030
10031             // The "/i" suffix allows caller to use a locale-specific formatting string.
10032             // Clean the format string by removing all but numerals and the decimal separator.
10033             // Then split the format string into pre and post decimal segments according to *what* the
10034             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
10035             if (formatString.substr(formatString.length - 2) == '/i') {
10036                 if (!I18NFormatCleanRe) {
10037                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
10038                 }
10039                 formatString = formatString.substr(0, formatString.length - 2);
10040                 i18n   = true;
10041                 hasComma = formatString.indexOf(comma) != -1;
10042                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
10043             } else {
10044                 hasComma = formatString.indexOf(',') != -1;
10045                 psplit = formatString.replace(formatCleanRe, '').split('.');
10046             }
10047
10048             if (1 < psplit.length) {
10049                 v = v.toFixed(psplit[1].length);
10050             } else if(2 < psplit.length) {
10051             } else {
10052                 v = v.toFixed(0);
10053             }
10054
10055             var fnum = v.toString();
10056
10057             psplit = fnum.split('.');
10058
10059             if (hasComma) {
10060                 var cnum = psplit[0],
10061                     parr = [],
10062                     j    = cnum.length,
10063                     m    = Math.floor(j / 3),
10064                     n    = cnum.length % 3 || 3,
10065                     i;
10066
10067                 for (i = 0; i < j; i += n) {
10068                     if (i !== 0) {
10069                         n = 3;
10070                     }
10071
10072                     parr[parr.length] = cnum.substr(i, n);
10073                     m -= 1;
10074                 }
10075                 fnum = parr.join(comma);
10076                 if (psplit[1]) {
10077                     fnum += dec + psplit[1];
10078                 }
10079             } else {
10080                 if (psplit[1]) {
10081                     fnum = psplit[0] + dec + psplit[1];
10082                 }
10083             }
10084
10085             if (neg) {
10086                 /*
10087                  * Edge case. If we have a very small negative number it will get rounded to 0,
10088                  * however the initial check at the top will still report as negative. Replace
10089                  * everything but 1-9 and check if the string is empty to determine a 0 value.
10090                  */
10091                 neg = fnum.replace(/[^1-9]/g, '') !== '';
10092             }
10093
10094             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
10095         },
10096
10097         /**
10098          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
10099          * @param {String} format Any valid number format string for {@link #number}
10100          * @return {Function} The number formatting function
10101          */
10102         numberRenderer : function(format) {
10103             return function(v) {
10104                 return UtilFormat.number(v, format);
10105             };
10106         },
10107
10108         /**
10109          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
10110          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
10111          * if the value is 0 or greater than 1.
10112          * @param {Number} value The value to compare against
10113          * @param {String} singular The singular form of the word
10114          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
10115          */
10116         plural : function(v, s, p) {
10117             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
10118         },
10119
10120         /**
10121          * Converts newline characters to the HTML tag &lt;br/>
10122          * @param {String} The string value to format.
10123          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
10124          */
10125         nl2br : function(v) {
10126             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
10127         },
10128
10129         /**
10130          * Alias for {@link Ext.String#capitalize}.
10131          * @method
10132          * @alias Ext.String#capitalize
10133          */
10134         capitalize: Ext.String.capitalize,
10135
10136         /**
10137          * Alias for {@link Ext.String#ellipsis}.
10138          * @method
10139          * @alias Ext.String#ellipsis
10140          */
10141         ellipsis: Ext.String.ellipsis,
10142
10143         /**
10144          * Alias for {@link Ext.String#format}.
10145          * @method
10146          * @alias Ext.String#format
10147          */
10148         format: Ext.String.format,
10149
10150         /**
10151          * Alias for {@link Ext.String#htmlDecode}.
10152          * @method
10153          * @alias Ext.String#htmlDecode
10154          */
10155         htmlDecode: Ext.String.htmlDecode,
10156
10157         /**
10158          * Alias for {@link Ext.String#htmlEncode}.
10159          * @method
10160          * @alias Ext.String#htmlEncode
10161          */
10162         htmlEncode: Ext.String.htmlEncode,
10163
10164         /**
10165          * Alias for {@link Ext.String#leftPad}.
10166          * @method
10167          * @alias Ext.String#leftPad
10168          */
10169         leftPad: Ext.String.leftPad,
10170
10171         /**
10172          * Alias for {@link Ext.String#trim}.
10173          * @method
10174          * @alias Ext.String#trim
10175          */
10176         trim : Ext.String.trim,
10177
10178         /**
10179          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
10180          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
10181          * @param {Number/String} v The encoded margins
10182          * @return {Object} An object with margin sizes for top, right, bottom and left
10183          */
10184         parseBox : function(box) {
10185             if (Ext.isNumber(box)) {
10186                 box = box.toString();
10187             }
10188             var parts  = box.split(' '),
10189                 ln = parts.length;
10190
10191             if (ln == 1) {
10192                 parts[1] = parts[2] = parts[3] = parts[0];
10193             }
10194             else if (ln == 2) {
10195                 parts[2] = parts[0];
10196                 parts[3] = parts[1];
10197             }
10198             else if (ln == 3) {
10199                 parts[3] = parts[1];
10200             }
10201
10202             return {
10203                 top   :parseInt(parts[0], 10) || 0,
10204                 right :parseInt(parts[1], 10) || 0,
10205                 bottom:parseInt(parts[2], 10) || 0,
10206                 left  :parseInt(parts[3], 10) || 0
10207             };
10208         },
10209
10210         /**
10211          * Escapes the passed string for use in a regular expression
10212          * @param {String} str
10213          * @return {String}
10214          */
10215         escapeRegex : function(s) {
10216             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
10217         }
10218     });
10219 })();
10220
10221 /**
10222  * @class Ext.util.TaskRunner
10223  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
10224  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
10225  * if needed, you can create separate instances of TaskRunner.  Any number of
10226  * separate tasks can be started at any time and will run independently of each
10227  * other. Example usage:
10228  * <pre><code>
10229 // Start a simple clock task that updates a div once per second
10230 var updateClock = function(){
10231     Ext.fly('clock').update(new Date().format('g:i:s A'));
10232
10233 var task = {
10234     run: updateClock,
10235     interval: 1000 //1 second
10236 }
10237 var runner = new Ext.util.TaskRunner();
10238 runner.start(task);
10239
10240 // equivalent using TaskManager
10241 Ext.TaskManager.start({
10242     run: updateClock,
10243     interval: 1000
10244 });
10245
10246  * </code></pre>
10247  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10248  * Also see {@link Ext.util.DelayedTask}. 
10249  * 
10250  * @constructor
10251  * @param {Number} [interval=10] The minimum precision in milliseconds supported by this TaskRunner instance
10252  */
10253 Ext.ns('Ext.util');
10254
10255 Ext.util.TaskRunner = function(interval) {
10256     interval = interval || 10;
10257     var tasks = [],
10258     removeQueue = [],
10259     id = 0,
10260     running = false,
10261
10262     // private
10263     stopThread = function() {
10264         running = false;
10265         clearInterval(id);
10266         id = 0;
10267     },
10268
10269     // private
10270     startThread = function() {
10271         if (!running) {
10272             running = true;
10273             id = setInterval(runTasks, interval);
10274         }
10275     },
10276
10277     // private
10278     removeTask = function(t) {
10279         removeQueue.push(t);
10280         if (t.onStop) {
10281             t.onStop.apply(t.scope || t);
10282         }
10283     },
10284
10285     // private
10286     runTasks = function() {
10287         var rqLen = removeQueue.length,
10288             now = new Date().getTime(),
10289             i;
10290
10291         if (rqLen > 0) {
10292             for (i = 0; i < rqLen; i++) {
10293                 Ext.Array.remove(tasks, removeQueue[i]);
10294             }
10295             removeQueue = [];
10296             if (tasks.length < 1) {
10297                 stopThread();
10298                 return;
10299             }
10300         }
10301         i = 0;
10302         var t,
10303             itime,
10304             rt,
10305             len = tasks.length;
10306         for (; i < len; ++i) {
10307             t = tasks[i];
10308             itime = now - t.taskRunTime;
10309             if (t.interval <= itime) {
10310                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
10311                 t.taskRunTime = now;
10312                 if (rt === false || t.taskRunCount === t.repeat) {
10313                     removeTask(t);
10314                     return;
10315                 }
10316             }
10317             if (t.duration && t.duration <= (now - t.taskStartTime)) {
10318                 removeTask(t);
10319             }
10320         }
10321     };
10322
10323     /**
10324      * Starts a new task.
10325      * @method start
10326      * @param {Object} task <p>A config object that supports the following properties:<ul>
10327      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
10328      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
10329      * current invocation count if not.</p>
10330      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
10331      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
10332      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
10333      * should be invoked.</div></li>
10334      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
10335      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
10336      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
10337      * <code>run</code> function. Defaults to the task config object.</div></li>
10338      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
10339      * the task before stopping automatically (defaults to indefinite).</div></li>
10340      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
10341      * stopping automatically (defaults to indefinite).</div></li>
10342      * </ul></p>
10343      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
10344      * that calculations based on the repeat count can be performed.</p>
10345      * @return {Object} The task
10346      */
10347     this.start = function(task) {
10348         tasks.push(task);
10349         task.taskStartTime = new Date().getTime();
10350         task.taskRunTime = 0;
10351         task.taskRunCount = 0;
10352         startThread();
10353         return task;
10354     };
10355
10356     /**
10357      * Stops an existing running task.
10358      * @method stop
10359      * @param {Object} task The task to stop
10360      * @return {Object} The task
10361      */
10362     this.stop = function(task) {
10363         removeTask(task);
10364         return task;
10365     };
10366
10367     /**
10368      * Stops all tasks that are currently running.
10369      * @method stopAll
10370      */
10371     this.stopAll = function() {
10372         stopThread();
10373         for (var i = 0, len = tasks.length; i < len; i++) {
10374             if (tasks[i].onStop) {
10375                 tasks[i].onStop();
10376             }
10377         }
10378         tasks = [];
10379         removeQueue = [];
10380     };
10381 };
10382
10383 /**
10384  * @class Ext.TaskManager
10385  * @extends Ext.util.TaskRunner
10386  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
10387  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
10388  * <pre><code>
10389 // Start a simple clock task that updates a div once per second
10390 var task = {
10391     run: function(){
10392         Ext.fly('clock').update(new Date().format('g:i:s A'));
10393     },
10394     interval: 1000 //1 second
10395 }
10396 Ext.TaskManager.start(task);
10397 </code></pre>
10398  * <p>See the {@link #start} method for details about how to configure a task object.</p>
10399  * @singleton
10400  */
10401 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
10402 /**
10403  * @class Ext.is
10404  * 
10405  * Determines information about the current platform the application is running on.
10406  * 
10407  * @singleton
10408  */
10409 Ext.is = {
10410     init : function(navigator) {
10411         var platforms = this.platforms,
10412             ln = platforms.length,
10413             i, platform;
10414
10415         navigator = navigator || window.navigator;
10416
10417         for (i = 0; i < ln; i++) {
10418             platform = platforms[i];
10419             this[platform.identity] = platform.regex.test(navigator[platform.property]);
10420         }
10421
10422         /**
10423          * @property Desktop True if the browser is running on a desktop machine
10424          * @type {Boolean}
10425          */
10426         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
10427         /**
10428          * @property Tablet True if the browser is running on a tablet (iPad)
10429          */
10430         this.Tablet = this.iPad;
10431         /**
10432          * @property Phone True if the browser is running on a phone.
10433          * @type {Boolean}
10434          */
10435         this.Phone = !this.Desktop && !this.Tablet;
10436         /**
10437          * @property iOS True if the browser is running on iOS
10438          * @type {Boolean}
10439          */
10440         this.iOS = this.iPhone || this.iPad || this.iPod;
10441         
10442         /**
10443          * @property Standalone Detects when application has been saved to homescreen.
10444          * @type {Boolean}
10445          */
10446         this.Standalone = !!window.navigator.standalone;
10447     },
10448     
10449     /**
10450      * @property iPhone True when the browser is running on a iPhone
10451      * @type {Boolean}
10452      */
10453     platforms: [{
10454         property: 'platform',
10455         regex: /iPhone/i,
10456         identity: 'iPhone'
10457     },
10458     
10459     /**
10460      * @property iPod True when the browser is running on a iPod
10461      * @type {Boolean}
10462      */
10463     {
10464         property: 'platform',
10465         regex: /iPod/i,
10466         identity: 'iPod'
10467     },
10468     
10469     /**
10470      * @property iPad True when the browser is running on a iPad
10471      * @type {Boolean}
10472      */
10473     {
10474         property: 'userAgent',
10475         regex: /iPad/i,
10476         identity: 'iPad'
10477     },
10478     
10479     /**
10480      * @property Blackberry True when the browser is running on a Blackberry
10481      * @type {Boolean}
10482      */
10483     {
10484         property: 'userAgent',
10485         regex: /Blackberry/i,
10486         identity: 'Blackberry'
10487     },
10488     
10489     /**
10490      * @property Android True when the browser is running on an Android device
10491      * @type {Boolean}
10492      */
10493     {
10494         property: 'userAgent',
10495         regex: /Android/i,
10496         identity: 'Android'
10497     },
10498     
10499     /**
10500      * @property Mac True when the browser is running on a Mac
10501      * @type {Boolean}
10502      */
10503     {
10504         property: 'platform',
10505         regex: /Mac/i,
10506         identity: 'Mac'
10507     },
10508     
10509     /**
10510      * @property Windows True when the browser is running on Windows
10511      * @type {Boolean}
10512      */
10513     {
10514         property: 'platform',
10515         regex: /Win/i,
10516         identity: 'Windows'
10517     },
10518     
10519     /**
10520      * @property Linux True when the browser is running on Linux
10521      * @type {Boolean}
10522      */
10523     {
10524         property: 'platform',
10525         regex: /Linux/i,
10526         identity: 'Linux'
10527     }]
10528 };
10529
10530 Ext.is.init();
10531
10532 /**
10533  * @class Ext.supports
10534  *
10535  * Determines information about features are supported in the current environment
10536  * 
10537  * @singleton
10538  */
10539 Ext.supports = {
10540     init : function() {
10541         var doc = document,
10542             div = doc.createElement('div'),
10543             tests = this.tests,
10544             ln = tests.length,
10545             i, test;
10546
10547         div.innerHTML = [
10548             '<div style="height:30px;width:50px;">',
10549                 '<div style="height:20px;width:20px;"></div>',
10550             '</div>',
10551             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
10552                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
10553             '</div>',
10554             '<div style="float:left; background-color:transparent;"></div>'
10555         ].join('');
10556
10557         doc.body.appendChild(div);
10558
10559         for (i = 0; i < ln; i++) {
10560             test = tests[i];
10561             this[test.identity] = test.fn.call(this, doc, div);
10562         }
10563
10564         doc.body.removeChild(div);
10565     },
10566
10567     /**
10568      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
10569      * @type {Boolean}
10570      */
10571     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
10572
10573     /**
10574      * @property ClassList True if document environment supports the HTML5 classList API.
10575      * @type {Boolean}
10576      */
10577     ClassList: !!document.documentElement.classList,
10578
10579     /**
10580      * @property OrientationChange True if the device supports orientation change
10581      * @type {Boolean}
10582      */
10583     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
10584     
10585     /**
10586      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
10587      * @type {Boolean}
10588      */
10589     DeviceMotion: ('ondevicemotion' in window),
10590     
10591     /**
10592      * @property Touch True if the device supports touch
10593      * @type {Boolean}
10594      */
10595     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
10596     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
10597     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
10598
10599     tests: [
10600         /**
10601          * @property Transitions True if the device supports CSS3 Transitions
10602          * @type {Boolean}
10603          */
10604         {
10605             identity: 'Transitions',
10606             fn: function(doc, div) {
10607                 var prefix = [
10608                         'webkit',
10609                         'Moz',
10610                         'o',
10611                         'ms',
10612                         'khtml'
10613                     ],
10614                     TE = 'TransitionEnd',
10615                     transitionEndName = [
10616                         prefix[0] + TE,
10617                         'transitionend', //Moz bucks the prefixing convention
10618                         prefix[2] + TE,
10619                         prefix[3] + TE,
10620                         prefix[4] + TE
10621                     ],
10622                     ln = prefix.length,
10623                     i = 0,
10624                     out = false;
10625                 div = Ext.get(div);
10626                 for (; i < ln; i++) {
10627                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
10628                         Ext.supports.CSS3Prefix = prefix[i];
10629                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
10630                         out = true;
10631                         break;
10632                     }
10633                 }
10634                 return out;
10635             }
10636         },
10637         
10638         /**
10639          * @property RightMargin True if the device supports right margin.
10640          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
10641          * @type {Boolean}
10642          */
10643         {
10644             identity: 'RightMargin',
10645             fn: function(doc, div) {
10646                 var view = doc.defaultView;
10647                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
10648             }
10649         },
10650
10651         /**
10652          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
10653          * selection when their display style is changed. Essentially, if a text input
10654          * has focus and its display style is changed, the I-beam disappears.
10655          * 
10656          * This bug is encountered due to the work around in place for the {@link #RightMargin}
10657          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
10658          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
10659          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
10660          */
10661         {
10662             identity: 'DisplayChangeInputSelectionBug',
10663             fn: function() {
10664                 var webKitVersion = Ext.webKitVersion;
10665                 // WebKit but older than Safari 5 or Chrome 6:
10666                 return 0 < webKitVersion && webKitVersion < 533;
10667             }
10668         },
10669
10670         /**
10671          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
10672          * selection when their display style is changed. Essentially, if a text area has
10673          * focus and its display style is changed, the I-beam disappears.
10674          *
10675          * This bug is encountered due to the work around in place for the {@link #RightMargin}
10676          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
10677          * be fixed in Chrome 11.
10678          */
10679         {
10680             identity: 'DisplayChangeTextAreaSelectionBug',
10681             fn: function() {
10682                 var webKitVersion = Ext.webKitVersion;
10683
10684                 /*
10685                 Has bug w/textarea:
10686
10687                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
10688                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
10689                             Safari/534.16
10690                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
10691                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
10692                             Safari/533.21.1
10693
10694                 No bug:
10695
10696                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
10697                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
10698                             Safari/534.24
10699                 */
10700                 return 0 < webKitVersion && webKitVersion < 534.24;
10701             }
10702         },
10703
10704         /**
10705          * @property TransparentColor True if the device supports transparent color
10706          * @type {Boolean}
10707          */
10708         {
10709             identity: 'TransparentColor',
10710             fn: function(doc, div, view) {
10711                 view = doc.defaultView;
10712                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
10713             }
10714         },
10715
10716         /**
10717          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
10718          * @type {Boolean}
10719          */
10720         {
10721             identity: 'ComputedStyle',
10722             fn: function(doc, div, view) {
10723                 view = doc.defaultView;
10724                 return view && view.getComputedStyle;
10725             }
10726         },
10727         
10728         /**
10729          * @property SVG True if the device supports SVG
10730          * @type {Boolean}
10731          */
10732         {
10733             identity: 'Svg',
10734             fn: function(doc) {
10735                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
10736             }
10737         },
10738     
10739         /**
10740          * @property Canvas True if the device supports Canvas
10741          * @type {Boolean}
10742          */
10743         {
10744             identity: 'Canvas',
10745             fn: function(doc) {
10746                 return !!doc.createElement('canvas').getContext;
10747             }
10748         },
10749         
10750         /**
10751          * @property VML True if the device supports VML
10752          * @type {Boolean}
10753          */
10754         {
10755             identity: 'Vml',
10756             fn: function(doc) {
10757                 var d = doc.createElement("div");
10758                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
10759                 return (d.childNodes.length == 2);
10760             }
10761         },
10762         
10763         /**
10764          * @property Float True if the device supports CSS float
10765          * @type {Boolean}
10766          */
10767         {
10768             identity: 'Float',
10769             fn: function(doc, div) {
10770                 return !!div.lastChild.style.cssFloat;
10771             }
10772         },
10773         
10774         /**
10775          * @property AudioTag True if the device supports the HTML5 audio tag
10776          * @type {Boolean}
10777          */
10778         {
10779             identity: 'AudioTag',
10780             fn: function(doc) {
10781                 return !!doc.createElement('audio').canPlayType;
10782             }
10783         },
10784         
10785         /**
10786          * @property History True if the device supports HTML5 history
10787          * @type {Boolean}
10788          */
10789         {
10790             identity: 'History',
10791             fn: function() {
10792                 return !!(window.history && history.pushState);
10793             }
10794         },
10795         
10796         /**
10797          * @property CSS3DTransform True if the device supports CSS3DTransform
10798          * @type {Boolean}
10799          */
10800         {
10801             identity: 'CSS3DTransform',
10802             fn: function() {
10803                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
10804             }
10805         },
10806
10807                 /**
10808          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
10809          * @type {Boolean}
10810          */
10811         {
10812             identity: 'CSS3LinearGradient',
10813             fn: function(doc, div) {
10814                 var property = 'background-image:',
10815                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
10816                     w3c      = 'linear-gradient(left top, black, white)',
10817                     moz      = '-moz-' + w3c,
10818                     options  = [property + webkit, property + w3c, property + moz];
10819                 
10820                 div.style.cssText = options.join(';');
10821                 
10822                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
10823             }
10824         },
10825         
10826         /**
10827          * @property CSS3BorderRadius True if the device supports CSS3 border radius
10828          * @type {Boolean}
10829          */
10830         {
10831             identity: 'CSS3BorderRadius',
10832             fn: function(doc, div) {
10833                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
10834                     pass = false,
10835                     i;
10836                 for (i = 0; i < domPrefixes.length; i++) {
10837                     if (document.body.style[domPrefixes[i]] !== undefined) {
10838                         return true;
10839                     }
10840                 }
10841                 return pass;
10842             }
10843         },
10844         
10845         /**
10846          * @property GeoLocation True if the device supports GeoLocation
10847          * @type {Boolean}
10848          */
10849         {
10850             identity: 'GeoLocation',
10851             fn: function() {
10852                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
10853             }
10854         },
10855         /**
10856          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
10857          * @type {Boolean}
10858          */
10859         {
10860             identity: 'MouseEnterLeave',
10861             fn: function(doc, div){
10862                 return ('onmouseenter' in div && 'onmouseleave' in div);
10863             }
10864         },
10865         /**
10866          * @property MouseWheel True if the browser supports the mousewheel event
10867          * @type {Boolean}
10868          */
10869         {
10870             identity: 'MouseWheel',
10871             fn: function(doc, div) {
10872                 return ('onmousewheel' in div);
10873             }
10874         },
10875         /**
10876          * @property Opacity True if the browser supports normal css opacity
10877          * @type {Boolean}
10878          */
10879         {
10880             identity: 'Opacity',
10881             fn: function(doc, div){
10882                 // Not a strict equal comparison in case opacity can be converted to a number.
10883                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
10884                     return false;
10885                 }
10886                 div.firstChild.style.cssText = 'opacity:0.73';
10887                 return div.firstChild.style.opacity == '0.73';
10888             }
10889         },
10890         /**
10891          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
10892          * @type {Boolean}
10893          */
10894         {
10895             identity: 'Placeholder',
10896             fn: function(doc) {
10897                 return 'placeholder' in doc.createElement('input');
10898             }
10899         },
10900         
10901         /**
10902          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
10903          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
10904          * @type {Boolean}
10905          */
10906         {
10907             identity: 'Direct2DBug',
10908             fn: function() {
10909                 return Ext.isString(document.body.style.msTransformOrigin);
10910             }
10911         },
10912         /**
10913          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
10914          * @type {Boolean}
10915          */
10916         {
10917             identity: 'BoundingClientRect',
10918             fn: function(doc, div) {
10919                 return Ext.isFunction(div.getBoundingClientRect);
10920             }
10921         },
10922         {
10923             identity: 'IncludePaddingInWidthCalculation',
10924             fn: function(doc, div){
10925                 var el = Ext.get(div.childNodes[1].firstChild);
10926                 return el.getWidth() == 210;
10927             }
10928         },
10929         {
10930             identity: 'IncludePaddingInHeightCalculation',
10931             fn: function(doc, div){
10932                 var el = Ext.get(div.childNodes[1].firstChild);
10933                 return el.getHeight() == 210;
10934             }
10935         },
10936         
10937         /**
10938          * @property ArraySort True if the Array sort native method isn't bugged.
10939          * @type {Boolean}
10940          */
10941         {
10942             identity: 'ArraySort',
10943             fn: function() {
10944                 var a = [1,2,3,4,5].sort(function(){ return 0; });
10945                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
10946             }
10947         },
10948         /**
10949          * @property Range True if browser support document.createRange native method.
10950          * @type {Boolean}
10951          */
10952         {
10953             identity: 'Range',
10954             fn: function() {
10955                 return !!document.createRange;
10956             }
10957         },
10958         /**
10959          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
10960          * @type {Boolean}
10961          */
10962         {
10963             identity: 'CreateContextualFragment',
10964             fn: function() {
10965                 var range = Ext.supports.Range ? document.createRange() : false;
10966                 
10967                 return range && !!range.createContextualFragment;
10968             }
10969         },
10970
10971         /**
10972          * @property WindowOnError True if browser supports window.onerror.
10973          * @type {Boolean}
10974          */
10975         {
10976             identity: 'WindowOnError',
10977             fn: function () {
10978                 // sadly, we cannot feature detect this...
10979                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
10980             }
10981         }
10982     ]
10983 };
10984
10985
10986
10987 /*
10988
10989 This file is part of Ext JS 4
10990
10991 Copyright (c) 2011 Sencha Inc
10992
10993 Contact:  http://www.sencha.com/contact
10994
10995 Commercial Usage
10996 Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
10997
10998 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
10999
11000 */
11001 /**
11002  * @class Ext.DomHelper
11003  * @alternateClassName Ext.core.DomHelper
11004  *
11005  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
11006  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11007  * from your DOM building code.</p>
11008  *
11009  * <p><b><u>DomHelper element specification object</u></b></p>
11010  * <p>A specification object is used when creating elements. Attributes of this object
11011  * are assumed to be element attributes, except for 4 special attributes:
11012  * <div class="mdetail-params"><ul>
11013  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
11014  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
11015  * same kind of element definition objects to be created and appended. These can be nested
11016  * as deep as you want.</div></li>
11017  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
11018  * This will end up being either the "class" attribute on a HTML fragment or className
11019  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
11020  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
11021  * </ul></div></p>
11022  * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
11023  * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
11024  * contains special characters that would not normally be allowed in a double-quoted attribute value,
11025  * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
11026  * malformed HTML being created. This behavior may change in a future release.</p>
11027  *
11028  * <p><b><u>Insertion methods</u></b></p>
11029  * <p>Commonly used insertion methods:
11030  * <div class="mdetail-params"><ul>
11031  * <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
11032  * <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
11033  * <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
11034  * <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
11035  * <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
11036  * <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
11037  * </ul></div></p>
11038  *
11039  * <p><b><u>Example</u></b></p>
11040  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
11041  * element with id <tt>'my-div'</tt>:<br>
11042  <pre><code>
11043 var dh = Ext.DomHelper; // create shorthand alias
11044 // specification object
11045 var spec = {
11046     id: 'my-ul',
11047     tag: 'ul',
11048     cls: 'my-list',
11049     // append children after creating
11050     children: [     // may also specify 'cn' instead of 'children'
11051         {tag: 'li', id: 'item0', html: 'List Item 0'},
11052         {tag: 'li', id: 'item1', html: 'List Item 1'},
11053         {tag: 'li', id: 'item2', html: 'List Item 2'}
11054     ]
11055 };
11056 var list = dh.append(
11057     'my-div', // the context element 'my-div' can either be the id or the actual node
11058     spec      // the specification object
11059 );
11060  </code></pre></p>
11061  * <p>Element creation specification parameters in this class may also be passed as an Array of
11062  * specification objects. This can be used to insert multiple sibling nodes into an existing
11063  * container very efficiently. For example, to add more list items to the example above:<pre><code>
11064 dh.append('my-ul', [
11065     {tag: 'li', id: 'item3', html: 'List Item 3'},
11066     {tag: 'li', id: 'item4', html: 'List Item 4'}
11067 ]);
11068  * </code></pre></p>
11069  *
11070  * <p><b><u>Templating</u></b></p>
11071  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
11072  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
11073  * insert new elements. Revisiting the example above, we could utilize templating this time:
11074  * <pre><code>
11075 // create the node
11076 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
11077 // get template
11078 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
11079
11080 for(var i = 0; i < 5, i++){
11081     tpl.append(list, [i]); // use template to append to the actual node
11082 }
11083  * </code></pre></p>
11084  * <p>An example using a template:<pre><code>
11085 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
11086
11087 var tpl = new Ext.DomHelper.createTemplate(html);
11088 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
11089 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
11090  * </code></pre></p>
11091  *
11092  * <p>The same example using named parameters:<pre><code>
11093 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11094
11095 var tpl = new Ext.DomHelper.createTemplate(html);
11096 tpl.append('blog-roll', {
11097     id: 'link1',
11098     url: 'http://www.edspencer.net/',
11099     text: "Ed&#39;s Site"
11100 });
11101 tpl.append('blog-roll', {
11102     id: 'link2',
11103     url: 'http://www.dustindiaz.com/',
11104     text: "Dustin&#39;s Site"
11105 });
11106  * </code></pre></p>
11107  *
11108  * <p><b><u>Compiling Templates</u></b></p>
11109  * <p>Templates are applied using regular expressions. The performance is great, but if
11110  * you are adding a bunch of DOM elements using the same template, you can increase
11111  * performance even further by {@link Ext.Template#compile "compiling"} the template.
11112  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
11113  * broken up at the different variable points and a dynamic function is created and eval'ed.
11114  * The generated function performs string concatenation of these parts and the passed
11115  * variables instead of using regular expressions.
11116  * <pre><code>
11117 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
11118
11119 var tpl = new Ext.DomHelper.createTemplate(html);
11120 tpl.compile();
11121
11122 //... use template like normal
11123  * </code></pre></p>
11124  *
11125  * <p><b><u>Performance Boost</u></b></p>
11126  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
11127  * of DOM can significantly boost performance.</p>
11128  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
11129  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
11130  * results in the creation of a text node. Usage:</p>
11131  * <pre><code>
11132 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
11133  * </code></pre>
11134  * @singleton
11135  */
11136 Ext.ns('Ext.core');
11137 Ext.core.DomHelper = Ext.DomHelper = function(){
11138     var tempTableEl = null,
11139         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
11140         tableRe = /^table|tbody|tr|td$/i,
11141         confRe = /tag|children|cn|html$/i,
11142         tableElRe = /td|tr|tbody/i,
11143         endRe = /end/i,
11144         pub,
11145         // kill repeat to save bytes
11146         afterbegin = 'afterbegin',
11147         afterend = 'afterend',
11148         beforebegin = 'beforebegin',
11149         beforeend = 'beforeend',
11150         ts = '<table>',
11151         te = '</table>',
11152         tbs = ts+'<tbody>',
11153         tbe = '</tbody>'+te,
11154         trs = tbs + '<tr>',
11155         tre = '</tr>'+tbe;
11156
11157     // private
11158     function doInsert(el, o, returnElement, pos, sibling, append){
11159         el = Ext.getDom(el);
11160         var newNode;
11161         if (pub.useDom) {
11162             newNode = createDom(o, null);
11163             if (append) {
11164                 el.appendChild(newNode);
11165             } else {
11166                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
11167             }
11168         } else {
11169             newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
11170         }
11171         return returnElement ? Ext.get(newNode, true) : newNode;
11172     }
11173
11174     function createDom(o, parentNode){
11175         var el,
11176             doc = document,
11177             useSet,
11178             attr,
11179             val,
11180             cn;
11181
11182         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
11183             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
11184             for (var i = 0, l = o.length; i < l; i++) {
11185                 createDom(o[i], el);
11186             }
11187         } else if (typeof o == 'string') {         // Allow a string as a child spec.
11188             el = doc.createTextNode(o);
11189         } else {
11190             el = doc.createElement( o.tag || 'div' );
11191             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
11192             for (attr in o) {
11193                 if(!confRe.test(attr)){
11194                     val = o[attr];
11195                     if(attr == 'cls'){
11196                         el.className = val;
11197                     }else{
11198                         if(useSet){
11199                             el.setAttribute(attr, val);
11200                         }else{
11201                             el[attr] = val;
11202                         }
11203                     }
11204                 }
11205             }
11206             Ext.DomHelper.applyStyles(el, o.style);
11207
11208             if ((cn = o.children || o.cn)) {
11209                 createDom(cn, el);
11210             } else if (o.html) {
11211                 el.innerHTML = o.html;
11212             }
11213         }
11214         if(parentNode){
11215            parentNode.appendChild(el);
11216         }
11217         return el;
11218     }
11219
11220     // build as innerHTML where available
11221     function createHtml(o){
11222         var b = '',
11223             attr,
11224             val,
11225             key,
11226             cn,
11227             i;
11228
11229         if(typeof o == "string"){
11230             b = o;
11231         } else if (Ext.isArray(o)) {
11232             for (i=0; i < o.length; i++) {
11233                 if(o[i]) {
11234                     b += createHtml(o[i]);
11235                 }
11236             }
11237         } else {
11238             b += '<' + (o.tag = o.tag || 'div');
11239             for (attr in o) {
11240                 val = o[attr];
11241                 if(!confRe.test(attr)){
11242                     if (typeof val == "object") {
11243                         b += ' ' + attr + '="';
11244                         for (key in val) {
11245                             b += key + ':' + val[key] + ';';
11246                         }
11247                         b += '"';
11248                     }else{
11249                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
11250                     }
11251                 }
11252             }
11253             // Now either just close the tag or try to add children and close the tag.
11254             if (emptyTags.test(o.tag)) {
11255                 b += '/>';
11256             } else {
11257                 b += '>';
11258                 if ((cn = o.children || o.cn)) {
11259                     b += createHtml(cn);
11260                 } else if(o.html){
11261                     b += o.html;
11262                 }
11263                 b += '</' + o.tag + '>';
11264             }
11265         }
11266         return b;
11267     }
11268
11269     function ieTable(depth, s, h, e){
11270         tempTableEl.innerHTML = [s, h, e].join('');
11271         var i = -1,
11272             el = tempTableEl,
11273             ns;
11274         while(++i < depth){
11275             el = el.firstChild;
11276         }
11277 //      If the result is multiple siblings, then encapsulate them into one fragment.
11278         ns = el.nextSibling;
11279         if (ns){
11280             var df = document.createDocumentFragment();
11281             while(el){
11282                 ns = el.nextSibling;
11283                 df.appendChild(el);
11284                 el = ns;
11285             }
11286             el = df;
11287         }
11288         return el;
11289     }
11290
11291     /**
11292      * @ignore
11293      * Nasty code for IE's broken table implementation
11294      */
11295     function insertIntoTable(tag, where, el, html) {
11296         var node,
11297             before;
11298
11299         tempTableEl = tempTableEl || document.createElement('div');
11300
11301         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
11302            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
11303             return null;
11304         }
11305         before = where == beforebegin ? el :
11306                  where == afterend ? el.nextSibling :
11307                  where == afterbegin ? el.firstChild : null;
11308
11309         if (where == beforebegin || where == afterend) {
11310             el = el.parentNode;
11311         }
11312
11313         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
11314             node = ieTable(4, trs, html, tre);
11315         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
11316                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
11317             node = ieTable(3, tbs, html, tbe);
11318         } else {
11319             node = ieTable(2, ts, html, te);
11320         }
11321         el.insertBefore(node, before);
11322         return node;
11323     }
11324
11325     /**
11326      * @ignore
11327      * Fix for IE9 createContextualFragment missing method
11328      */
11329     function createContextualFragment(html){
11330         var div = document.createElement("div"),
11331             fragment = document.createDocumentFragment(),
11332             i = 0,
11333             length, childNodes;
11334
11335         div.innerHTML = html;
11336         childNodes = div.childNodes;
11337         length = childNodes.length;
11338
11339         for (; i < length; i++) {
11340             fragment.appendChild(childNodes[i].cloneNode(true));
11341         }
11342
11343         return fragment;
11344     }
11345
11346     pub = {
11347         /**
11348          * Returns the markup for the passed Element(s) config.
11349          * @param {Object} o The DOM object spec (and children)
11350          * @return {String}
11351          */
11352         markup : function(o){
11353             return createHtml(o);
11354         },
11355
11356         /**
11357          * Applies a style specification to an element.
11358          * @param {String/HTMLElement} el The element to apply styles to
11359          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
11360          * a function which returns such a specification.
11361          */
11362         applyStyles : function(el, styles){
11363             if (styles) {
11364                 el = Ext.fly(el);
11365                 if (typeof styles == "function") {
11366                     styles = styles.call();
11367                 }
11368                 if (typeof styles == "string") {
11369                     styles = Ext.Element.parseStyles(styles);
11370                 }
11371                 if (typeof styles == "object") {
11372                     el.setStyle(styles);
11373                 }
11374             }
11375         },
11376
11377         /**
11378          * Inserts an HTML fragment into the DOM.
11379          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
11380          *
11381          * For example take the following HTML: `<div>Contents</div>`
11382          *
11383          * Using different `where` values inserts element to the following places:
11384          *
11385          * - beforeBegin: `<HERE><div>Contents</div>`
11386          * - afterBegin: `<div><HERE>Contents</div>`
11387          * - beforeEnd: `<div>Contents<HERE></div>`
11388          * - afterEnd: `<div>Contents</div><HERE>`
11389          *
11390          * @param {HTMLElement/TextNode} el The context element
11391          * @param {String} html The HTML fragment
11392          * @return {HTMLElement} The new node
11393          */
11394         insertHtml : function(where, el, html){
11395             var hash = {},
11396                 hashVal,
11397                 range,
11398                 rangeEl,
11399                 setStart,
11400                 frag,
11401                 rs;
11402
11403             where = where.toLowerCase();
11404             // add these here because they are used in both branches of the condition.
11405             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
11406             hash[afterend] = ['AfterEnd', 'nextSibling'];
11407
11408             // if IE and context element is an HTMLElement
11409             if (el.insertAdjacentHTML) {
11410                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
11411                     return rs;
11412                 }
11413
11414                 // add these two to the hash.
11415                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
11416                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
11417                 if ((hashVal = hash[where])) {
11418                     el.insertAdjacentHTML(hashVal[0], html);
11419                     return el[hashVal[1]];
11420                 }
11421             // if (not IE and context element is an HTMLElement) or TextNode
11422             } else {
11423                 // we cannot insert anything inside a textnode so...
11424                 if (Ext.isTextNode(el)) {
11425                     where = where === 'afterbegin' ? 'beforebegin' : where;
11426                     where = where === 'beforeend' ? 'afterend' : where;
11427                 }
11428                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
11429                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
11430                 if (hash[where]) {
11431                     if (range) {
11432                         range[setStart](el);
11433                         frag = range.createContextualFragment(html);
11434                     } else {
11435                         frag = createContextualFragment(html);
11436                     }
11437                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
11438                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
11439                 } else {
11440                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
11441                     if (el.firstChild) {
11442                         if (range) {
11443                             range[setStart](el[rangeEl]);
11444                             frag = range.createContextualFragment(html);
11445                         } else {
11446                             frag = createContextualFragment(html);
11447                         }
11448
11449                         if(where == afterbegin){
11450                             el.insertBefore(frag, el.firstChild);
11451                         }else{
11452                             el.appendChild(frag);
11453                         }
11454                     } else {
11455                         el.innerHTML = html;
11456                     }
11457                     return el[rangeEl];
11458                 }
11459             }
11460         },
11461
11462         /**
11463          * Creates new DOM element(s) and inserts them before el.
11464          * @param {String/HTMLElement/Ext.Element} el The context element
11465          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11466          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11467          * @return {HTMLElement/Ext.Element} The new node
11468          */
11469         insertBefore : function(el, o, returnElement){
11470             return doInsert(el, o, returnElement, beforebegin);
11471         },
11472
11473         /**
11474          * Creates new DOM element(s) and inserts them after el.
11475          * @param {String/HTMLElement/Ext.Element} el The context element
11476          * @param {Object} o The DOM object spec (and children)
11477          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11478          * @return {HTMLElement/Ext.Element} The new node
11479          */
11480         insertAfter : function(el, o, returnElement){
11481             return doInsert(el, o, returnElement, afterend, 'nextSibling');
11482         },
11483
11484         /**
11485          * Creates new DOM element(s) and inserts them as the first child of el.
11486          * @param {String/HTMLElement/Ext.Element} el The context element
11487          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11488          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11489          * @return {HTMLElement/Ext.Element} The new node
11490          */
11491         insertFirst : function(el, o, returnElement){
11492             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
11493         },
11494
11495         /**
11496          * Creates new DOM element(s) and appends them to el.
11497          * @param {String/HTMLElement/Ext.Element} el The context element
11498          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11499          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11500          * @return {HTMLElement/Ext.Element} The new node
11501          */
11502         append : function(el, o, returnElement){
11503             return doInsert(el, o, returnElement, beforeend, '', true);
11504         },
11505
11506         /**
11507          * Creates new DOM element(s) and overwrites the contents of el with them.
11508          * @param {String/HTMLElement/Ext.Element} el The context element
11509          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11510          * @param {Boolean} returnElement (optional) true to return a Ext.Element
11511          * @return {HTMLElement/Ext.Element} The new node
11512          */
11513         overwrite : function(el, o, returnElement){
11514             el = Ext.getDom(el);
11515             el.innerHTML = createHtml(o);
11516             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
11517         },
11518
11519         createHtml : createHtml,
11520
11521         /**
11522          * Creates new DOM element(s) without inserting them to the document.
11523          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
11524          * @return {HTMLElement} The new uninserted node
11525          * @method
11526          */
11527         createDom: createDom,
11528
11529         /** True to force the use of DOM instead of html fragments @type Boolean */
11530         useDom : false,
11531
11532         /**
11533          * Creates a new Ext.Template from the DOM object spec.
11534          * @param {Object} o The DOM object spec (and children)
11535          * @return {Ext.Template} The new template
11536          */
11537         createTemplate : function(o){
11538             var html = Ext.DomHelper.createHtml(o);
11539             return Ext.create('Ext.Template', html);
11540         }
11541     };
11542     return pub;
11543 }();
11544
11545 /*
11546  * This is code is also distributed under MIT license for use
11547  * with jQuery and prototype JavaScript libraries.
11548  */
11549 /**
11550  * @class Ext.DomQuery
11551 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).
11552 <p>
11553 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>
11554
11555 <p>
11556 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.
11557 </p>
11558 <h4>Element Selectors:</h4>
11559 <ul class="list">
11560     <li> <b>*</b> any element</li>
11561     <li> <b>E</b> an element with the tag E</li>
11562     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
11563     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
11564     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
11565     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
11566 </ul>
11567 <h4>Attribute Selectors:</h4>
11568 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
11569 <ul class="list">
11570     <li> <b>E[foo]</b> has an attribute "foo"</li>
11571     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
11572     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
11573     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
11574     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
11575     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
11576     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
11577 </ul>
11578 <h4>Pseudo Classes:</h4>
11579 <ul class="list">
11580     <li> <b>E:first-child</b> E is the first child of its parent</li>
11581     <li> <b>E:last-child</b> E is the last child of its parent</li>
11582     <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>
11583     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
11584     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
11585     <li> <b>E:only-child</b> E is the only child of its parent</li>
11586     <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>
11587     <li> <b>E:first</b> the first E in the resultset</li>
11588     <li> <b>E:last</b> the last E in the resultset</li>
11589     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
11590     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
11591     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
11592     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
11593     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
11594     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
11595     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
11596     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
11597     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
11598     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
11599 </ul>
11600 <h4>CSS Value Selectors:</h4>
11601 <ul class="list">
11602     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
11603     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
11604     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
11605     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
11606     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
11607     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
11608 </ul>
11609  * @singleton
11610  */
11611 Ext.ns('Ext.core');
11612
11613 Ext.core.DomQuery = Ext.DomQuery = function(){
11614     var cache = {},
11615         simpleCache = {},
11616         valueCache = {},
11617         nonSpace = /\S/,
11618         trimRe = /^\s+|\s+$/g,
11619         tplRe = /\{(\d+)\}/g,
11620         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
11621         tagTokenRe = /^(#)?([\w-\*]+)/,
11622         nthRe = /(\d*)n\+?(\d*)/,
11623         nthRe2 = /\D/,
11624         startIdRe = /^\s*\#/,
11625         // This is for IE MSXML which does not support expandos.
11626     // IE runs the same speed using setAttribute, however FF slows way down
11627     // and Safari completely fails so they need to continue to use expandos.
11628     isIE = window.ActiveXObject ? true : false,
11629     key = 30803;
11630
11631     // this eval is stop the compressor from
11632     // renaming the variable to something shorter
11633     eval("var batch = 30803;");
11634
11635     // Retrieve the child node from a particular
11636     // parent at the specified index.
11637     function child(parent, index){
11638         var i = 0,
11639             n = parent.firstChild;
11640         while(n){
11641             if(n.nodeType == 1){
11642                if(++i == index){
11643                    return n;
11644                }
11645             }
11646             n = n.nextSibling;
11647         }
11648         return null;
11649     }
11650
11651     // retrieve the next element node
11652     function next(n){
11653         while((n = n.nextSibling) && n.nodeType != 1);
11654         return n;
11655     }
11656
11657     // retrieve the previous element node
11658     function prev(n){
11659         while((n = n.previousSibling) && n.nodeType != 1);
11660         return n;
11661     }
11662
11663     // Mark each child node with a nodeIndex skipping and
11664     // removing empty text nodes.
11665     function children(parent){
11666         var n = parent.firstChild,
11667         nodeIndex = -1,
11668         nextNode;
11669         while(n){
11670             nextNode = n.nextSibling;
11671             // clean worthless empty nodes.
11672             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
11673             parent.removeChild(n);
11674             }else{
11675             // add an expando nodeIndex
11676             n.nodeIndex = ++nodeIndex;
11677             }
11678             n = nextNode;
11679         }
11680         return this;
11681     }
11682
11683
11684     // nodeSet - array of nodes
11685     // cls - CSS Class
11686     function byClassName(nodeSet, cls){
11687         if(!cls){
11688             return nodeSet;
11689         }
11690         var result = [], ri = -1;
11691         for(var i = 0, ci; ci = nodeSet[i]; i++){
11692             if((' '+ci.className+' ').indexOf(cls) != -1){
11693                 result[++ri] = ci;
11694             }
11695         }
11696         return result;
11697     };
11698
11699     function attrValue(n, attr){
11700         // if its an array, use the first node.
11701         if(!n.tagName && typeof n.length != "undefined"){
11702             n = n[0];
11703         }
11704         if(!n){
11705             return null;
11706         }
11707
11708         if(attr == "for"){
11709             return n.htmlFor;
11710         }
11711         if(attr == "class" || attr == "className"){
11712             return n.className;
11713         }
11714         return n.getAttribute(attr) || n[attr];
11715
11716     };
11717
11718
11719     // ns - nodes
11720     // mode - false, /, >, +, ~
11721     // tagName - defaults to "*"
11722     function getNodes(ns, mode, tagName){
11723         var result = [], ri = -1, cs;
11724         if(!ns){
11725             return result;
11726         }
11727         tagName = tagName || "*";
11728         // convert to array
11729         if(typeof ns.getElementsByTagName != "undefined"){
11730             ns = [ns];
11731         }
11732
11733         // no mode specified, grab all elements by tagName
11734         // at any depth
11735         if(!mode){
11736             for(var i = 0, ni; ni = ns[i]; i++){
11737                 cs = ni.getElementsByTagName(tagName);
11738                 for(var j = 0, ci; ci = cs[j]; j++){
11739                     result[++ri] = ci;
11740                 }
11741             }
11742         // Direct Child mode (/ or >)
11743         // E > F or E/F all direct children elements of E that have the tag
11744         } else if(mode == "/" || mode == ">"){
11745             var utag = tagName.toUpperCase();
11746             for(var i = 0, ni, cn; ni = ns[i]; i++){
11747                 cn = ni.childNodes;
11748                 for(var j = 0, cj; cj = cn[j]; j++){
11749                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
11750                         result[++ri] = cj;
11751                     }
11752                 }
11753             }
11754         // Immediately Preceding mode (+)
11755         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
11756         }else if(mode == "+"){
11757             var utag = tagName.toUpperCase();
11758             for(var i = 0, n; n = ns[i]; i++){
11759                 while((n = n.nextSibling) && n.nodeType != 1);
11760                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
11761                     result[++ri] = n;
11762                 }
11763             }
11764         // Sibling mode (~)
11765         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
11766         }else if(mode == "~"){
11767             var utag = tagName.toUpperCase();
11768             for(var i = 0, n; n = ns[i]; i++){
11769                 while((n = n.nextSibling)){
11770                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
11771                         result[++ri] = n;
11772                     }
11773                 }
11774             }
11775         }
11776         return result;
11777     }
11778
11779     function concat(a, b){
11780         if(b.slice){
11781             return a.concat(b);
11782         }
11783         for(var i = 0, l = b.length; i < l; i++){
11784             a[a.length] = b[i];
11785         }
11786         return a;
11787     }
11788
11789     function byTag(cs, tagName){
11790         if(cs.tagName || cs == document){
11791             cs = [cs];
11792         }
11793         if(!tagName){
11794             return cs;
11795         }
11796         var result = [], ri = -1;
11797         tagName = tagName.toLowerCase();
11798         for(var i = 0, ci; ci = cs[i]; i++){
11799             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
11800                 result[++ri] = ci;
11801             }
11802         }
11803         return result;
11804     }
11805
11806     function byId(cs, id){
11807         if(cs.tagName || cs == document){
11808             cs = [cs];
11809         }
11810         if(!id){
11811             return cs;
11812         }
11813         var result = [], ri = -1;
11814         for(var i = 0, ci; ci = cs[i]; i++){
11815             if(ci && ci.id == id){
11816                 result[++ri] = ci;
11817                 return result;
11818             }
11819         }
11820         return result;
11821     }
11822
11823     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
11824     // custom can be "{"
11825     function byAttribute(cs, attr, value, op, custom){
11826         var result = [],
11827             ri = -1,
11828             useGetStyle = custom == "{",
11829             fn = Ext.DomQuery.operators[op],
11830             a,
11831             xml,
11832             hasXml;
11833
11834         for(var i = 0, ci; ci = cs[i]; i++){
11835             // skip non-element nodes.
11836             if(ci.nodeType != 1){
11837                 continue;
11838             }
11839             // only need to do this for the first node
11840             if(!hasXml){
11841                 xml = Ext.DomQuery.isXml(ci);
11842                 hasXml = true;
11843             }
11844
11845             // we only need to change the property names if we're dealing with html nodes, not XML
11846             if(!xml){
11847                 if(useGetStyle){
11848                     a = Ext.DomQuery.getStyle(ci, attr);
11849                 } else if (attr == "class" || attr == "className"){
11850                     a = ci.className;
11851                 } else if (attr == "for"){
11852                     a = ci.htmlFor;
11853                 } else if (attr == "href"){
11854                     // getAttribute href bug
11855                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
11856                     a = ci.getAttribute("href", 2);
11857                 } else{
11858                     a = ci.getAttribute(attr);
11859                 }
11860             }else{
11861                 a = ci.getAttribute(attr);
11862             }
11863             if((fn && fn(a, value)) || (!fn && a)){
11864                 result[++ri] = ci;
11865             }
11866         }
11867         return result;
11868     }
11869
11870     function byPseudo(cs, name, value){
11871         return Ext.DomQuery.pseudos[name](cs, value);
11872     }
11873
11874     function nodupIEXml(cs){
11875         var d = ++key,
11876             r;
11877         cs[0].setAttribute("_nodup", d);
11878         r = [cs[0]];
11879         for(var i = 1, len = cs.length; i < len; i++){
11880             var c = cs[i];
11881             if(!c.getAttribute("_nodup") != d){
11882                 c.setAttribute("_nodup", d);
11883                 r[r.length] = c;
11884             }
11885         }
11886         for(var i = 0, len = cs.length; i < len; i++){
11887             cs[i].removeAttribute("_nodup");
11888         }
11889         return r;
11890     }
11891
11892     function nodup(cs){
11893         if(!cs){
11894             return [];
11895         }
11896         var len = cs.length, c, i, r = cs, cj, ri = -1;
11897         if(!len || typeof cs.nodeType != "undefined" || len == 1){
11898             return cs;
11899         }
11900         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
11901             return nodupIEXml(cs);
11902         }
11903         var d = ++key;
11904         cs[0]._nodup = d;
11905         for(i = 1; c = cs[i]; i++){
11906             if(c._nodup != d){
11907                 c._nodup = d;
11908             }else{
11909                 r = [];
11910                 for(var j = 0; j < i; j++){
11911                     r[++ri] = cs[j];
11912                 }
11913                 for(j = i+1; cj = cs[j]; j++){
11914                     if(cj._nodup != d){
11915                         cj._nodup = d;
11916                         r[++ri] = cj;
11917                     }
11918                 }
11919                 return r;
11920             }
11921         }
11922         return r;
11923     }
11924
11925     function quickDiffIEXml(c1, c2){
11926         var d = ++key,
11927             r = [];
11928         for(var i = 0, len = c1.length; i < len; i++){
11929             c1[i].setAttribute("_qdiff", d);
11930         }
11931         for(var i = 0, len = c2.length; i < len; i++){
11932             if(c2[i].getAttribute("_qdiff") != d){
11933                 r[r.length] = c2[i];
11934             }
11935         }
11936         for(var i = 0, len = c1.length; i < len; i++){
11937            c1[i].removeAttribute("_qdiff");
11938         }
11939         return r;
11940     }
11941
11942     function quickDiff(c1, c2){
11943         var len1 = c1.length,
11944             d = ++key,
11945             r = [];
11946         if(!len1){
11947             return c2;
11948         }
11949         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
11950             return quickDiffIEXml(c1, c2);
11951         }
11952         for(var i = 0; i < len1; i++){
11953             c1[i]._qdiff = d;
11954         }
11955         for(var i = 0, len = c2.length; i < len; i++){
11956             if(c2[i]._qdiff != d){
11957                 r[r.length] = c2[i];
11958             }
11959         }
11960         return r;
11961     }
11962
11963     function quickId(ns, mode, root, id){
11964         if(ns == root){
11965            var d = root.ownerDocument || root;
11966            return d.getElementById(id);
11967         }
11968         ns = getNodes(ns, mode, "*");
11969         return byId(ns, id);
11970     }
11971
11972     return {
11973         getStyle : function(el, name){
11974             return Ext.fly(el).getStyle(name);
11975         },
11976         /**
11977          * Compiles a selector/xpath query into a reusable function. The returned function
11978          * takes one parameter "root" (optional), which is the context node from where the query should start.
11979          * @param {String} selector The selector/xpath query
11980          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
11981          * @return {Function}
11982          */
11983         compile : function(path, type){
11984             type = type || "select";
11985
11986             // setup fn preamble
11987             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
11988                 mode,
11989                 lastPath,
11990                 matchers = Ext.DomQuery.matchers,
11991                 matchersLn = matchers.length,
11992                 modeMatch,
11993                 // accept leading mode switch
11994                 lmode = path.match(modeRe);
11995
11996             if(lmode && lmode[1]){
11997                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
11998                 path = path.replace(lmode[1], "");
11999             }
12000
12001             // strip leading slashes
12002             while(path.substr(0, 1)=="/"){
12003                 path = path.substr(1);
12004             }
12005
12006             while(path && lastPath != path){
12007                 lastPath = path;
12008                 var tokenMatch = path.match(tagTokenRe);
12009                 if(type == "select"){
12010                     if(tokenMatch){
12011                         // ID Selector
12012                         if(tokenMatch[1] == "#"){
12013                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
12014                         }else{
12015                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
12016                         }
12017                         path = path.replace(tokenMatch[0], "");
12018                     }else if(path.substr(0, 1) != '@'){
12019                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
12020                     }
12021                 // type of "simple"
12022                 }else{
12023                     if(tokenMatch){
12024                         if(tokenMatch[1] == "#"){
12025                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
12026                         }else{
12027                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
12028                         }
12029                         path = path.replace(tokenMatch[0], "");
12030                     }
12031                 }
12032                 while(!(modeMatch = path.match(modeRe))){
12033                     var matched = false;
12034                     for(var j = 0; j < matchersLn; j++){
12035                         var t = matchers[j];
12036                         var m = path.match(t.re);
12037                         if(m){
12038                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
12039                                 return m[i];
12040                             });
12041                             path = path.replace(m[0], "");
12042                             matched = true;
12043                             break;
12044                         }
12045                     }
12046                     // prevent infinite loop on bad selector
12047                     if(!matched){
12048                     }
12049                 }
12050                 if(modeMatch[1]){
12051                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
12052                     path = path.replace(modeMatch[1], "");
12053                 }
12054             }
12055             // close fn out
12056             fn[fn.length] = "return nodup(n);\n}";
12057
12058             // eval fn and return it
12059             eval(fn.join(""));
12060             return f;
12061         },
12062
12063         /**
12064          * Selects an array of DOM nodes using JavaScript-only implementation.
12065          *
12066          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
12067          *
12068          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
12069          * @param {HTMLElement/String} root (optional) The start of the query (defaults to document).
12070          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12071          * no matches, and empty Array is returned.
12072          */
12073         jsSelect: function(path, root, type){
12074             // set root to doc if not specified.
12075             root = root || document;
12076
12077             if(typeof root == "string"){
12078                 root = document.getElementById(root);
12079             }
12080             var paths = path.split(","),
12081                 results = [];
12082
12083             // loop over each selector
12084             for(var i = 0, len = paths.length; i < len; i++){
12085                 var subPath = paths[i].replace(trimRe, "");
12086                 // compile and place in cache
12087                 if(!cache[subPath]){
12088                     cache[subPath] = Ext.DomQuery.compile(subPath);
12089                     if(!cache[subPath]){
12090                     }
12091                 }
12092                 var result = cache[subPath](root);
12093                 if(result && result != document){
12094                     results = results.concat(result);
12095                 }
12096             }
12097
12098             // if there were multiple selectors, make sure dups
12099             // are eliminated
12100             if(paths.length > 1){
12101                 return nodup(results);
12102             }
12103             return results;
12104         },
12105
12106         isXml: function(el) {
12107             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
12108             return docEl ? docEl.nodeName !== "HTML" : false;
12109         },
12110
12111         /**
12112          * Selects an array of DOM nodes by CSS/XPath selector.
12113          *
12114          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
12115          * {@link Ext.DomQuery#jsSelect} to do the work.
12116          *
12117          * Aliased as {@link Ext#query}.
12118          *
12119          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
12120          *
12121          * @param {String} path The selector/xpath query
12122          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12123          * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
12124          * Empty array when no matches.
12125          * @method
12126          */
12127         select : document.querySelectorAll ? function(path, root, type) {
12128             root = root || document;
12129             /* 
12130              * Safari 3.x can't handle uppercase or unicode characters when in quirks mode.
12131              */
12132             if (!Ext.DomQuery.isXml(root) && !(Ext.isSafari3 && !Ext.isStrict)) { 
12133                 try {
12134                     /*
12135                      * This checking here is to "fix" the behaviour of querySelectorAll
12136                      * for non root document queries. The way qsa works is intentional,
12137                      * however it's definitely not the expected way it should work.
12138                      * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/
12139                      *
12140                      * We only modify the path for single selectors (ie, no multiples),
12141                      * without a full parser it makes it difficult to do this correctly.
12142                      */
12143                     var isDocumentRoot = root.nodeType === 9,
12144                         _path = path,
12145                         _root = root;
12146
12147                     if (!isDocumentRoot && path.indexOf(',') === -1 && !startIdRe.test(path)) {
12148                         _path = '#' + Ext.id(root) + ' ' + path;
12149                         _root = root.parentNode;
12150                     }
12151                     return Ext.Array.toArray(_root.querySelectorAll(_path));
12152                 }
12153                 catch (e) {
12154                 }
12155             }
12156             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12157         } : function(path, root, type) {
12158             return Ext.DomQuery.jsSelect.call(this, path, root, type);
12159         },
12160
12161         /**
12162          * Selects a single element.
12163          * @param {String} selector The selector/xpath query
12164          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12165          * @return {HTMLElement} The DOM element which matched the selector.
12166          */
12167         selectNode : function(path, root){
12168             return Ext.DomQuery.select(path, root)[0];
12169         },
12170
12171         /**
12172          * Selects the value of a node, optionally replacing null with the defaultValue.
12173          * @param {String} selector The selector/xpath query
12174          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12175          * @param {String} defaultValue (optional) When specified, this is return as empty value.
12176          * @return {String}
12177          */
12178         selectValue : function(path, root, defaultValue){
12179             path = path.replace(trimRe, "");
12180             if(!valueCache[path]){
12181                 valueCache[path] = Ext.DomQuery.compile(path, "select");
12182             }
12183             var n = valueCache[path](root), v;
12184             n = n[0] ? n[0] : n;
12185
12186             // overcome a limitation of maximum textnode size
12187             // Rumored to potentially crash IE6 but has not been confirmed.
12188             // http://reference.sitepoint.com/javascript/Node/normalize
12189             // https://developer.mozilla.org/En/DOM/Node.normalize
12190             if (typeof n.normalize == 'function') n.normalize();
12191
12192             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
12193             return ((v === null||v === undefined||v==='') ? defaultValue : v);
12194         },
12195
12196         /**
12197          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
12198          * @param {String} selector The selector/xpath query
12199          * @param {HTMLElement} root (optional) The start of the query (defaults to document).
12200          * @param {Number} defaultValue (optional) When specified, this is return as empty value.
12201          * @return {Number}
12202          */
12203         selectNumber : function(path, root, defaultValue){
12204             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
12205             return parseFloat(v);
12206         },
12207
12208         /**
12209          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
12210          * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements
12211          * @param {String} selector The simple selector to test
12212          * @return {Boolean}
12213          */
12214         is : function(el, ss){
12215             if(typeof el == "string"){
12216                 el = document.getElementById(el);
12217             }
12218             var isArray = Ext.isArray(el),
12219                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
12220             return isArray ? (result.length == el.length) : (result.length > 0);
12221         },
12222
12223         /**
12224          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
12225          * @param {HTMLElement[]} el An array of elements to filter
12226          * @param {String} selector The simple selector to test
12227          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
12228          * the selector instead of the ones that match
12229          * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
12230          * no matches, and empty Array is returned.
12231          */
12232         filter : function(els, ss, nonMatches){
12233             ss = ss.replace(trimRe, "");
12234             if(!simpleCache[ss]){
12235                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
12236             }
12237             var result = simpleCache[ss](els);
12238             return nonMatches ? quickDiff(result, els) : result;
12239         },
12240
12241         /**
12242          * Collection of matching regular expressions and code snippets.
12243          * Each capture group within () will be replace the {} in the select
12244          * statement as specified by their index.
12245          */
12246         matchers : [{
12247                 re: /^\.([\w-]+)/,
12248                 select: 'n = byClassName(n, " {1} ");'
12249             }, {
12250                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
12251                 select: 'n = byPseudo(n, "{1}", "{2}");'
12252             },{
12253                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
12254                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
12255             }, {
12256                 re: /^#([\w-]+)/,
12257                 select: 'n = byId(n, "{1}");'
12258             },{
12259                 re: /^@([\w-]+)/,
12260                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
12261             }
12262         ],
12263
12264         /**
12265          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
12266          * 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;.
12267          */
12268         operators : {
12269             "=" : function(a, v){
12270                 return a == v;
12271             },
12272             "!=" : function(a, v){
12273                 return a != v;
12274             },
12275             "^=" : function(a, v){
12276                 return a && a.substr(0, v.length) == v;
12277             },
12278             "$=" : function(a, v){
12279                 return a && a.substr(a.length-v.length) == v;
12280             },
12281             "*=" : function(a, v){
12282                 return a && a.indexOf(v) !== -1;
12283             },
12284             "%=" : function(a, v){
12285                 return (a % v) == 0;
12286             },
12287             "|=" : function(a, v){
12288                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
12289             },
12290             "~=" : function(a, v){
12291                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
12292             }
12293         },
12294
12295         /**
12296 Object hash of "pseudo class" filter functions which are used when filtering selections.
12297 Each function is passed two parameters:
12298
12299 - **c** : Array
12300     An Array of DOM elements to filter.
12301
12302 - **v** : String
12303     The argument (if any) supplied in the selector.
12304
12305 A filter function returns an Array of DOM elements which conform to the pseudo class.
12306 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
12307 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
12308
12309 For example, to filter `a` elements to only return links to __external__ resources:
12310
12311     Ext.DomQuery.pseudos.external = function(c, v){
12312         var r = [], ri = -1;
12313         for(var i = 0, ci; ci = c[i]; i++){
12314             // Include in result set only if it's a link to an external resource
12315             if(ci.hostname != location.hostname){
12316                 r[++ri] = ci;
12317             }
12318         }
12319         return r;
12320     };
12321
12322 Then external links could be gathered with the following statement:
12323
12324     var externalLinks = Ext.select("a:external");
12325
12326         * @markdown
12327         */
12328         pseudos : {
12329             "first-child" : function(c){
12330                 var r = [], ri = -1, n;
12331                 for(var i = 0, ci; ci = n = c[i]; i++){
12332                     while((n = n.previousSibling) && n.nodeType != 1);
12333                     if(!n){
12334                         r[++ri] = ci;
12335                     }
12336                 }
12337                 return r;
12338             },
12339
12340             "last-child" : function(c){
12341                 var r = [], ri = -1, n;
12342                 for(var i = 0, ci; ci = n = c[i]; i++){
12343                     while((n = n.nextSibling) && n.nodeType != 1);
12344                     if(!n){
12345                         r[++ri] = ci;
12346                     }
12347                 }
12348                 return r;
12349             },
12350
12351             "nth-child" : function(c, a) {
12352                 var r = [], ri = -1,
12353                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
12354                     f = (m[1] || 1) - 0, l = m[2] - 0;
12355                 for(var i = 0, n; n = c[i]; i++){
12356                     var pn = n.parentNode;
12357                     if (batch != pn._batch) {
12358                         var j = 0;
12359                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
12360                             if(cn.nodeType == 1){
12361                                cn.nodeIndex = ++j;
12362                             }
12363                         }
12364                         pn._batch = batch;
12365                     }
12366                     if (f == 1) {
12367                         if (l == 0 || n.nodeIndex == l){
12368                             r[++ri] = n;
12369                         }
12370                     } else if ((n.nodeIndex + l) % f == 0){
12371                         r[++ri] = n;
12372                     }
12373                 }
12374
12375                 return r;
12376             },
12377
12378             "only-child" : function(c){
12379                 var r = [], ri = -1;;
12380                 for(var i = 0, ci; ci = c[i]; i++){
12381                     if(!prev(ci) && !next(ci)){
12382                         r[++ri] = ci;
12383                     }
12384                 }
12385                 return r;
12386             },
12387
12388             "empty" : function(c){
12389                 var r = [], ri = -1;
12390                 for(var i = 0, ci; ci = c[i]; i++){
12391                     var cns = ci.childNodes, j = 0, cn, empty = true;
12392                     while(cn = cns[j]){
12393                         ++j;
12394                         if(cn.nodeType == 1 || cn.nodeType == 3){
12395                             empty = false;
12396                             break;
12397                         }
12398                     }
12399                     if(empty){
12400                         r[++ri] = ci;
12401                     }
12402                 }
12403                 return r;
12404             },
12405
12406             "contains" : function(c, v){
12407                 var r = [], ri = -1;
12408                 for(var i = 0, ci; ci = c[i]; i++){
12409                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
12410                         r[++ri] = ci;
12411                     }
12412                 }
12413                 return r;
12414             },
12415
12416             "nodeValue" : function(c, v){
12417                 var r = [], ri = -1;
12418                 for(var i = 0, ci; ci = c[i]; i++){
12419                     if(ci.firstChild && ci.firstChild.nodeValue == v){
12420                         r[++ri] = ci;
12421                     }
12422                 }
12423                 return r;
12424             },
12425
12426             "checked" : function(c){
12427                 var r = [], ri = -1;
12428                 for(var i = 0, ci; ci = c[i]; i++){
12429                     if(ci.checked == true){
12430                         r[++ri] = ci;
12431                     }
12432                 }
12433                 return r;
12434             },
12435
12436             "not" : function(c, ss){
12437                 return Ext.DomQuery.filter(c, ss, true);
12438             },
12439
12440             "any" : function(c, selectors){
12441                 var ss = selectors.split('|'),
12442                     r = [], ri = -1, s;
12443                 for(var i = 0, ci; ci = c[i]; i++){
12444                     for(var j = 0; s = ss[j]; j++){
12445                         if(Ext.DomQuery.is(ci, s)){
12446                             r[++ri] = ci;
12447                             break;
12448                         }
12449                     }
12450                 }
12451                 return r;
12452             },
12453
12454             "odd" : function(c){
12455                 return this["nth-child"](c, "odd");
12456             },
12457
12458             "even" : function(c){
12459                 return this["nth-child"](c, "even");
12460             },
12461
12462             "nth" : function(c, a){
12463                 return c[a-1] || [];
12464             },
12465
12466             "first" : function(c){
12467                 return c[0] || [];
12468             },
12469
12470             "last" : function(c){
12471                 return c[c.length-1] || [];
12472             },
12473
12474             "has" : function(c, ss){
12475                 var s = Ext.DomQuery.select,
12476                     r = [], ri = -1;
12477                 for(var i = 0, ci; ci = c[i]; i++){
12478                     if(s(ss, ci).length > 0){
12479                         r[++ri] = ci;
12480                     }
12481                 }
12482                 return r;
12483             },
12484
12485             "next" : function(c, ss){
12486                 var is = Ext.DomQuery.is,
12487                     r = [], ri = -1;
12488                 for(var i = 0, ci; ci = c[i]; i++){
12489                     var n = next(ci);
12490                     if(n && is(n, ss)){
12491                         r[++ri] = ci;
12492                     }
12493                 }
12494                 return r;
12495             },
12496
12497             "prev" : function(c, ss){
12498                 var is = Ext.DomQuery.is,
12499                     r = [], ri = -1;
12500                 for(var i = 0, ci; ci = c[i]; i++){
12501                     var n = prev(ci);
12502                     if(n && is(n, ss)){
12503                         r[++ri] = ci;
12504                     }
12505                 }
12506                 return r;
12507             }
12508         }
12509     };
12510 }();
12511
12512 /**
12513  * Shorthand of {@link Ext.DomQuery#select}
12514  * @member Ext
12515  * @method query
12516  * @alias Ext.DomQuery#select
12517  */
12518 Ext.query = Ext.DomQuery.select;
12519
12520 /**
12521  * @class Ext.Element
12522  * @alternateClassName Ext.core.Element
12523  *
12524  * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
12525  *
12526  * All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all
12527  * DOM elements.
12528  *
12529  * Note that the events documented in this class are not Ext events, they encapsulate browser events. Some older browsers
12530  * may not support the full range of events. Which events are supported is beyond the control of Ext JS.
12531  *
12532  * Usage:
12533  *
12534  *     // by id
12535  *     var el = Ext.get("my-div");
12536  *
12537  *     // by DOM element reference
12538  *     var el = Ext.get(myDivElement);
12539  *
12540  * # Animations
12541  *
12542  * When an element is manipulated, by default there is no animation.
12543  *
12544  *     var el = Ext.get("my-div");
12545  *
12546  *     // no animation
12547  *     el.setWidth(100);
12548  *
12549  * Many of the functions for manipulating an element have an optional "animate" parameter. This parameter can be
12550  * specified as boolean (true) for default animation effects.
12551  *
12552  *     // default animation
12553  *     el.setWidth(100, true);
12554  *
12555  * To configure the effects, an object literal with animation options to use as the Element animation configuration
12556  * object can also be specified. Note that the supported Element animation configuration options are a subset of the
12557  * {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
12558  * are:
12559  *
12560  *     Option    Default   Description
12561  *     --------- --------  ---------------------------------------------
12562  *     {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
12563  *     {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
12564  *     {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
12565  *     {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
12566  *
12567  * Usage:
12568  *
12569  *     // Element animation options object
12570  *     var opt = {
12571  *         {@link Ext.fx.Anim#duration duration}: 1,
12572  *         {@link Ext.fx.Anim#easing easing}: 'elasticIn',
12573  *         {@link Ext.fx.Anim#callback callback}: this.foo,
12574  *         {@link Ext.fx.Anim#scope scope}: this
12575  *     };
12576  *     // animation with some options set
12577  *     el.setWidth(100, opt);
12578  *
12579  * The Element animation object being used for the animation will be set on the options object as "anim", which allows
12580  * you to stop or manipulate the animation. Here is an example:
12581  *
12582  *     // using the "anim" property to get the Anim object
12583  *     if(opt.anim.isAnimated()){
12584  *         opt.anim.stop();
12585  *     }
12586  *
12587  * # Composite (Collections of) Elements
12588  *
12589  * For working with collections of Elements, see {@link Ext.CompositeElement}
12590  *
12591  * @constructor
12592  * Creates new Element directly.
12593  * @param {String/HTMLElement} element
12594  * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this
12595  * element in the cache and if there is it returns the same instance. This will skip that check (useful for extending
12596  * this class).
12597  * @return {Object}
12598  */
12599  (function() {
12600     var DOC = document,
12601         EC = Ext.cache;
12602
12603     Ext.Element = Ext.core.Element = function(element, forceNew) {
12604         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
12605         id;
12606
12607         if (!dom) {
12608             return null;
12609         }
12610
12611         id = dom.id;
12612
12613         if (!forceNew && id && EC[id]) {
12614             // element object already exists
12615             return EC[id].el;
12616         }
12617
12618         /**
12619          * @property {HTMLElement} dom
12620          * The DOM element
12621          */
12622         this.dom = dom;
12623
12624         /**
12625          * @property {String} id
12626          * The DOM element ID
12627          */
12628         this.id = id || Ext.id(dom);
12629     };
12630
12631     var DH = Ext.DomHelper,
12632     El = Ext.Element;
12633
12634
12635     El.prototype = {
12636         /**
12637          * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
12638          * @param {Object} o The object with the attributes
12639          * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
12640          * @return {Ext.Element} this
12641          */
12642         set: function(o, useSet) {
12643             var el = this.dom,
12644                 attr,
12645                 val;
12646             useSet = (useSet !== false) && !!el.setAttribute;
12647
12648             for (attr in o) {
12649                 if (o.hasOwnProperty(attr)) {
12650                     val = o[attr];
12651                     if (attr == 'style') {
12652                         DH.applyStyles(el, val);
12653                     } else if (attr == 'cls') {
12654                         el.className = val;
12655                     } else if (useSet) {
12656                         el.setAttribute(attr, val);
12657                     } else {
12658                         el[attr] = val;
12659                     }
12660                 }
12661             }
12662             return this;
12663         },
12664
12665         //  Mouse events
12666         /**
12667          * @event click
12668          * Fires when a mouse click is detected within the element.
12669          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12670          * @param {HTMLElement} t The target of the event.
12671          */
12672         /**
12673          * @event contextmenu
12674          * Fires when a right click is detected within the element.
12675          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12676          * @param {HTMLElement} t The target of the event.
12677          */
12678         /**
12679          * @event dblclick
12680          * Fires when a mouse double click is detected within the element.
12681          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12682          * @param {HTMLElement} t The target of the event.
12683          */
12684         /**
12685          * @event mousedown
12686          * Fires when a mousedown is detected within the element.
12687          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12688          * @param {HTMLElement} t The target of the event.
12689          */
12690         /**
12691          * @event mouseup
12692          * Fires when a mouseup is detected within the element.
12693          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12694          * @param {HTMLElement} t The target of the event.
12695          */
12696         /**
12697          * @event mouseover
12698          * Fires when a mouseover is detected within the element.
12699          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12700          * @param {HTMLElement} t The target of the event.
12701          */
12702         /**
12703          * @event mousemove
12704          * Fires when a mousemove is detected with the element.
12705          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12706          * @param {HTMLElement} t The target of the event.
12707          */
12708         /**
12709          * @event mouseout
12710          * Fires when a mouseout is detected with the element.
12711          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12712          * @param {HTMLElement} t The target of the event.
12713          */
12714         /**
12715          * @event mouseenter
12716          * Fires when the mouse enters the element.
12717          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12718          * @param {HTMLElement} t The target of the event.
12719          */
12720         /**
12721          * @event mouseleave
12722          * Fires when the mouse leaves the element.
12723          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12724          * @param {HTMLElement} t The target of the event.
12725          */
12726
12727         //  Keyboard events
12728         /**
12729          * @event keypress
12730          * Fires when a keypress is detected within the element.
12731          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12732          * @param {HTMLElement} t The target of the event.
12733          */
12734         /**
12735          * @event keydown
12736          * Fires when a keydown is detected within the element.
12737          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12738          * @param {HTMLElement} t The target of the event.
12739          */
12740         /**
12741          * @event keyup
12742          * Fires when a keyup is detected within the element.
12743          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12744          * @param {HTMLElement} t The target of the event.
12745          */
12746
12747
12748         //  HTML frame/object events
12749         /**
12750          * @event load
12751          * Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
12752          * objects and images.
12753          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12754          * @param {HTMLElement} t The target of the event.
12755          */
12756         /**
12757          * @event unload
12758          * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
12759          * element or any of its content has been removed.
12760          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12761          * @param {HTMLElement} t The target of the event.
12762          */
12763         /**
12764          * @event abort
12765          * Fires when an object/image is stopped from loading before completely loaded.
12766          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12767          * @param {HTMLElement} t The target of the event.
12768          */
12769         /**
12770          * @event error
12771          * Fires when an object/image/frame cannot be loaded properly.
12772          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12773          * @param {HTMLElement} t The target of the event.
12774          */
12775         /**
12776          * @event resize
12777          * Fires when a document view is resized.
12778          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12779          * @param {HTMLElement} t The target of the event.
12780          */
12781         /**
12782          * @event scroll
12783          * Fires when a document view is scrolled.
12784          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12785          * @param {HTMLElement} t The target of the event.
12786          */
12787
12788         //  Form events
12789         /**
12790          * @event select
12791          * Fires when a user selects some text in a text field, including input and textarea.
12792          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12793          * @param {HTMLElement} t The target of the event.
12794          */
12795         /**
12796          * @event change
12797          * Fires when a control loses the input focus and its value has been modified since gaining focus.
12798          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12799          * @param {HTMLElement} t The target of the event.
12800          */
12801         /**
12802          * @event submit
12803          * Fires when a form is submitted.
12804          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12805          * @param {HTMLElement} t The target of the event.
12806          */
12807         /**
12808          * @event reset
12809          * Fires when a form is reset.
12810          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12811          * @param {HTMLElement} t The target of the event.
12812          */
12813         /**
12814          * @event focus
12815          * Fires when an element receives focus either via the pointing device or by tab navigation.
12816          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12817          * @param {HTMLElement} t The target of the event.
12818          */
12819         /**
12820          * @event blur
12821          * Fires when an element loses focus either via the pointing device or by tabbing navigation.
12822          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12823          * @param {HTMLElement} t The target of the event.
12824          */
12825
12826         //  User Interface events
12827         /**
12828          * @event DOMFocusIn
12829          * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
12830          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12831          * @param {HTMLElement} t The target of the event.
12832          */
12833         /**
12834          * @event DOMFocusOut
12835          * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
12836          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12837          * @param {HTMLElement} t The target of the event.
12838          */
12839         /**
12840          * @event DOMActivate
12841          * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
12842          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12843          * @param {HTMLElement} t The target of the event.
12844          */
12845
12846         //  DOM Mutation events
12847         /**
12848          * @event DOMSubtreeModified
12849          * Where supported. Fires when the subtree is modified.
12850          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12851          * @param {HTMLElement} t The target of the event.
12852          */
12853         /**
12854          * @event DOMNodeInserted
12855          * Where supported. Fires when a node has been added as a child of another node.
12856          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12857          * @param {HTMLElement} t The target of the event.
12858          */
12859         /**
12860          * @event DOMNodeRemoved
12861          * Where supported. Fires when a descendant node of the element is removed.
12862          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12863          * @param {HTMLElement} t The target of the event.
12864          */
12865         /**
12866          * @event DOMNodeRemovedFromDocument
12867          * Where supported. Fires when a node is being removed from a document.
12868          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12869          * @param {HTMLElement} t The target of the event.
12870          */
12871         /**
12872          * @event DOMNodeInsertedIntoDocument
12873          * Where supported. Fires when a node is being inserted into a document.
12874          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12875          * @param {HTMLElement} t The target of the event.
12876          */
12877         /**
12878          * @event DOMAttrModified
12879          * Where supported. Fires when an attribute has been modified.
12880          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12881          * @param {HTMLElement} t The target of the event.
12882          */
12883         /**
12884          * @event DOMCharacterDataModified
12885          * Where supported. Fires when the character data has been modified.
12886          * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12887          * @param {HTMLElement} t The target of the event.
12888          */
12889
12890         /**
12891          * @property {String} defaultUnit
12892          * The default unit to append to CSS values where a unit isn't provided.
12893          */
12894         defaultUnit: "px",
12895
12896         /**
12897          * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
12898          * @param {String} selector The simple selector to test
12899          * @return {Boolean} True if this element matches the selector, else false
12900          */
12901         is: function(simpleSelector) {
12902             return Ext.DomQuery.is(this.dom, simpleSelector);
12903         },
12904
12905         /**
12906          * Tries to focus the element. Any exceptions are caught and ignored.
12907          * @param {Number} defer (optional) Milliseconds to defer the focus
12908          * @return {Ext.Element} this
12909          */
12910         focus: function(defer,
12911                         /* private */
12912                         dom) {
12913             var me = this;
12914             dom = dom || me.dom;
12915             try {
12916                 if (Number(defer)) {
12917                     Ext.defer(me.focus, defer, null, [null, dom]);
12918                 } else {
12919                     dom.focus();
12920                 }
12921             } catch(e) {}
12922             return me;
12923         },
12924
12925         /**
12926          * Tries to blur the element. Any exceptions are caught and ignored.
12927          * @return {Ext.Element} this
12928          */
12929         blur: function() {
12930             try {
12931                 this.dom.blur();
12932             } catch(e) {}
12933             return this;
12934         },
12935
12936         /**
12937          * Returns the value of the "value" attribute
12938          * @param {Boolean} asNumber true to parse the value as a number
12939          * @return {String/Number}
12940          */
12941         getValue: function(asNumber) {
12942             var val = this.dom.value;
12943             return asNumber ? parseInt(val, 10) : val;
12944         },
12945
12946         /**
12947          * Appends an event handler to this element.
12948          *
12949          * @param {String} eventName The name of event to handle.
12950          *
12951          * @param {Function} fn The handler function the event invokes. This function is passed the following parameters:
12952          *
12953          * - **evt** : EventObject
12954          *
12955          *   The {@link Ext.EventObject EventObject} describing the event.
12956          *
12957          * - **el** : HtmlElement
12958          *
12959          *   The DOM element which was the target of the event. Note that this may be filtered by using the delegate option.
12960          *
12961          * - **o** : Object
12962          *
12963          *   The options object from the addListener call.
12964          *
12965          * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed. **If
12966          * omitted, defaults to this Element.**
12967          *
12968          * @param {Object} options (optional) An object containing handler configuration properties. This may contain any of
12969          * the following properties:
12970          *
12971          * - **scope** Object :
12972          *
12973          *   The scope (**this** reference) in which the handler function is executed. **If omitted, defaults to this
12974          *   Element.**
12975          *
12976          * - **delegate** String:
12977          *
12978          *   A simple selector to filter the target or look for a descendant of the target. See below for additional details.
12979          *
12980          * - **stopEvent** Boolean:
12981          *
12982          *   True to stop the event. That is stop propagation, and prevent the default action.
12983          *
12984          * - **preventDefault** Boolean:
12985          *
12986          *   True to prevent the default action
12987          *
12988          * - **stopPropagation** Boolean:
12989          *
12990          *   True to prevent event propagation
12991          *
12992          * - **normalized** Boolean:
12993          *
12994          *   False to pass a browser event to the handler function instead of an Ext.EventObject
12995          *
12996          * - **target** Ext.Element:
12997          *
12998          *   Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a
12999          *   child node.
13000          *
13001          * - **delay** Number:
13002          *
13003          *   The number of milliseconds to delay the invocation of the handler after the event fires.
13004          *
13005          * - **single** Boolean:
13006          *
13007          *   True to add a handler to handle just the next firing of the event, and then remove itself.
13008          *
13009          * - **buffer** Number:
13010          *
13011          *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
13012          *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
13013          *   handler is scheduled in its place.
13014          *
13015          * **Combining Options**
13016          *
13017          * In the following examples, the shorthand form {@link #on} is used rather than the more verbose addListener. The
13018          * two are equivalent. Using the options argument, it is possible to combine different types of listeners:
13019          *
13020          * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the options
13021          * object. The options object is available as the third parameter in the handler function.
13022          *
13023          * Code:
13024          *
13025          *     el.on('click', this.onClick, this, {
13026          *         single: true,
13027          *         delay: 100,
13028          *         stopEvent : true,
13029          *         forumId: 4
13030          *     });
13031          *
13032          * **Attaching multiple handlers in 1 call**
13033          *
13034          * The method also allows for a single argument to be passed which is a config object containing properties which
13035          * specify multiple handlers.
13036          *
13037          * Code:
13038          *
13039          *     el.on({
13040          *         'click' : {
13041          *             fn: this.onClick,
13042          *             scope: this,
13043          *             delay: 100
13044          *         },
13045          *         'mouseover' : {
13046          *             fn: this.onMouseOver,
13047          *             scope: this
13048          *         },
13049          *         'mouseout' : {
13050          *             fn: this.onMouseOut,
13051          *             scope: this
13052          *         }
13053          *     });
13054          *
13055          * Or a shorthand syntax:
13056          *
13057          * Code:
13058          *
13059          *     el.on({
13060          *         'click' : this.onClick,
13061          *         'mouseover' : this.onMouseOver,
13062          *         'mouseout' : this.onMouseOut,
13063          *         scope: this
13064          *     });
13065          *
13066          * **delegate**
13067          *
13068          * This is a configuration option that you can pass along when registering a handler for an event to assist with
13069          * event delegation. Event delegation is a technique that is used to reduce memory consumption and prevent exposure
13070          * to memory-leaks. By registering an event for a container element as opposed to each element within a container.
13071          * By setting this configuration option to a simple selector, the target element will be filtered to look for a
13072          * descendant of the target. For example:
13073          *
13074          *     // using this markup:
13075          *     <div id='elId'>
13076          *         <p id='p1'>paragraph one</p>
13077          *         <p id='p2' class='clickable'>paragraph two</p>
13078          *         <p id='p3'>paragraph three</p>
13079          *     </div>
13080          *
13081          *     // utilize event delegation to registering just one handler on the container element:
13082          *     el = Ext.get('elId');
13083          *     el.on(
13084          *         'click',
13085          *         function(e,t) {
13086          *             // handle click
13087          *             console.info(t.id); // 'p2'
13088          *         },
13089          *         this,
13090          *         {
13091          *             // filter the target element to be a descendant with the class 'clickable'
13092          *             delegate: '.clickable'
13093          *         }
13094          *     );
13095          *
13096          * @return {Ext.Element} this
13097          */
13098         addListener: function(eventName, fn, scope, options) {
13099             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
13100             return this;
13101         },
13102
13103         /**
13104          * Removes an event handler from this element.
13105          *
13106          * **Note**: if a *scope* was explicitly specified when {@link #addListener adding} the listener,
13107          * the same scope must be specified here.
13108          *
13109          * Example:
13110          *
13111          *     el.removeListener('click', this.handlerFn);
13112          *     // or
13113          *     el.un('click', this.handlerFn);
13114          *
13115          * @param {String} eventName The name of the event from which to remove the handler.
13116          * @param {Function} fn The handler function to remove. **This must be a reference to the function passed into the
13117          * {@link #addListener} call.**
13118          * @param {Object} scope If a scope (**this** reference) was specified when the listener was added, then this must
13119          * refer to the same object.
13120          * @return {Ext.Element} this
13121          */
13122         removeListener: function(eventName, fn, scope) {
13123             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
13124             return this;
13125         },
13126
13127         /**
13128          * Removes all previous added listeners from this element
13129          * @return {Ext.Element} this
13130          */
13131         removeAllListeners: function() {
13132             Ext.EventManager.removeAll(this.dom);
13133             return this;
13134         },
13135
13136         /**
13137          * Recursively removes all previous added listeners from this element and its children
13138          * @return {Ext.Element} this
13139          */
13140         purgeAllListeners: function() {
13141             Ext.EventManager.purgeElement(this);
13142             return this;
13143         },
13144
13145         /**
13146          * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
13147          * @param size {Mixed} The size to set
13148          * @param units {String} The units to append to a numeric size value
13149          * @private
13150          */
13151         addUnits: function(size, units) {
13152
13153             // Most common case first: Size is set to a number
13154             if (Ext.isNumber(size)) {
13155                 return size + (units || this.defaultUnit || 'px');
13156             }
13157
13158             // Size set to a value which means "auto"
13159             if (size === "" || size == "auto" || size == null) {
13160                 return size || '';
13161             }
13162
13163             // Otherwise, warn if it's not a valid CSS measurement
13164             if (!unitPattern.test(size)) {
13165                 return size || '';
13166             }
13167             return size;
13168         },
13169
13170         /**
13171          * Tests various css rules/browsers to determine if this element uses a border box
13172          * @return {Boolean}
13173          */
13174         isBorderBox: function() {
13175             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
13176         },
13177
13178         /**
13179          * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13180          * Ext.removeNode}
13181          */
13182         remove: function() {
13183             var me = this,
13184             dom = me.dom;
13185
13186             if (dom) {
13187                 delete me.dom;
13188                 Ext.removeNode(dom);
13189             }
13190         },
13191
13192         /**
13193          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
13194          * @param {Function} overFn The function to call when the mouse enters the Element.
13195          * @param {Function} outFn The function to call when the mouse leaves the Element.
13196          * @param {Object} scope (optional) The scope (`this` reference) in which the functions are executed. Defaults
13197          * to the Element's DOM element.
13198          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the
13199          * options parameter}.
13200          * @return {Ext.Element} this
13201          */
13202         hover: function(overFn, outFn, scope, options) {
13203             var me = this;
13204             me.on('mouseenter', overFn, scope || me.dom, options);
13205             me.on('mouseleave', outFn, scope || me.dom, options);
13206             return me;
13207         },
13208
13209         /**
13210          * Returns true if this element is an ancestor of the passed element
13211          * @param {HTMLElement/String} el The element to check
13212          * @return {Boolean} True if this element is an ancestor of el, else false
13213          */
13214         contains: function(el) {
13215             return ! el ? false: Ext.Element.isAncestor(this.dom, el.dom ? el.dom: el);
13216         },
13217
13218         /**
13219          * Returns the value of a namespaced attribute from the element's underlying DOM node.
13220          * @param {String} namespace The namespace in which to look for the attribute
13221          * @param {String} name The attribute name
13222          * @return {String} The attribute value
13223          */
13224         getAttributeNS: function(ns, name) {
13225             return this.getAttribute(name, ns);
13226         },
13227
13228         /**
13229          * Returns the value of an attribute from the element's underlying DOM node.
13230          * @param {String} name The attribute name
13231          * @param {String} namespace (optional) The namespace in which to look for the attribute
13232          * @return {String} The attribute value
13233          * @method
13234          */
13235         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
13236         function(name, ns) {
13237             var d = this.dom,
13238             type;
13239             if(ns) {
13240                 type = typeof d[ns + ":" + name];
13241                 if (type != 'undefined' && type != 'unknown') {
13242                     return d[ns + ":" + name] || null;
13243                 }
13244                 return null;
13245             }
13246             if (name === "for") {
13247                 name = "htmlFor";
13248             }
13249             return d[name] || null;
13250         }: function(name, ns) {
13251             var d = this.dom;
13252             if (ns) {
13253                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
13254             }
13255             return  d.getAttribute(name) || d[name] || null;
13256         },
13257
13258         /**
13259          * Update the innerHTML of this element
13260          * @param {String} html The new HTML
13261          * @return {Ext.Element} this
13262          */
13263         update: function(html) {
13264             if (this.dom) {
13265                 this.dom.innerHTML = html;
13266             }
13267             return this;
13268         }
13269     };
13270
13271     var ep = El.prototype;
13272
13273     El.addMethods = function(o) {
13274         Ext.apply(ep, o);
13275     };
13276
13277     /**
13278      * @method
13279      * @alias Ext.Element#addListener
13280      * Shorthand for {@link #addListener}.
13281      */
13282     ep.on = ep.addListener;
13283
13284     /**
13285      * @method
13286      * @alias Ext.Element#removeListener
13287      * Shorthand for {@link #removeListener}.
13288      */
13289     ep.un = ep.removeListener;
13290
13291     /**
13292      * @method
13293      * @alias Ext.Element#removeAllListeners
13294      * Alias for {@link #removeAllListeners}.
13295      */
13296     ep.clearListeners = ep.removeAllListeners;
13297
13298     /**
13299      * @method destroy
13300      * @member Ext.Element
13301      * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
13302      * Ext.removeNode}. Alias to {@link #remove}.
13303      */
13304     ep.destroy = ep.remove;
13305
13306     /**
13307      * @property {Boolean} autoBoxAdjust
13308      * true to automatically adjust width and height settings for box-model issues (default to true)
13309      */
13310     ep.autoBoxAdjust = true;
13311
13312     // private
13313     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
13314     docEl;
13315
13316     /**
13317      * Retrieves Ext.Element objects. {@link Ext#get} is an alias for {@link Ext.Element#get}.
13318      *
13319      * **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.Element
13320      * objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
13321      *
13322      * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
13323      * the same id via AJAX or DOM.
13324      *
13325      * @param {String/HTMLElement/Ext.Element} el The id of the node, a DOM Node or an existing Element.
13326      * @return {Ext.Element} The Element object (or null if no matching element was found)
13327      * @static
13328      */
13329     El.get = function(el) {
13330         var ex,
13331         elm,
13332         id;
13333         if (!el) {
13334             return null;
13335         }
13336         if (typeof el == "string") {
13337             // element id
13338             if (! (elm = DOC.getElementById(el))) {
13339                 return null;
13340             }
13341             if (EC[el] && EC[el].el) {
13342                 ex = EC[el].el;
13343                 ex.dom = elm;
13344             } else {
13345                 ex = El.addToCache(new El(elm));
13346             }
13347             return ex;
13348         } else if (el.tagName) {
13349             // dom element
13350             if (! (id = el.id)) {
13351                 id = Ext.id(el);
13352             }
13353             if (EC[id] && EC[id].el) {
13354                 ex = EC[id].el;
13355                 ex.dom = el;
13356             } else {
13357                 ex = El.addToCache(new El(el));
13358             }
13359             return ex;
13360         } else if (el instanceof El) {
13361             if (el != docEl) {
13362                 // refresh dom element in case no longer valid,
13363                 // catch case where it hasn't been appended
13364                 // If an el instance is passed, don't pass to getElementById without some kind of id
13365                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
13366                     el.dom = el.dom;
13367                 } else {
13368                     el.dom = DOC.getElementById(el.id) || el.dom;
13369                 }
13370             }
13371             return el;
13372         } else if (el.isComposite) {
13373             return el;
13374         } else if (Ext.isArray(el)) {
13375             return El.select(el);
13376         } else if (el == DOC) {
13377             // create a bogus element object representing the document object
13378             if (!docEl) {
13379                 var f = function() {};
13380                 f.prototype = El.prototype;
13381                 docEl = new f();
13382                 docEl.dom = DOC;
13383             }
13384             return docEl;
13385         }
13386         return null;
13387     };
13388
13389     /**
13390      * Retrieves Ext.Element objects like {@link Ext#get} but is optimized for sub-elements.
13391      * This is helpful for performance, because in IE (prior to IE 9), `getElementById` uses
13392      * an non-optimized search. In those browsers, starting the search for an element with a
13393      * matching ID at a parent of that element will greatly speed up the process.
13394      *
13395      * Unlike {@link Ext#get}, this method only accepts ID's. If the ID is not a child of
13396      * this element, it will still be found if it exists in the document, but will be slower
13397      * than calling {@link Ext#get} directly.
13398      *
13399      * @param {String} id The id of the element to get.
13400      * @return {Ext.Element} The Element object (or null if no matching element was found)
13401      * @member Ext.Element
13402      * @method getById
13403      * @markdown
13404      */
13405     ep.getById = (!Ext.isIE6 && !Ext.isIE7 && !Ext.isIE8) ? El.get :
13406         function (id) {
13407             var dom = this.dom,
13408                 cached, el, ret;
13409
13410             if (dom) {
13411                 el = dom.all[id];
13412                 if (el) {
13413                     // calling El.get here is a real hit (2x slower) because it has to
13414                     // redetermine that we are giving it a dom el.
13415                     cached = EC[id];
13416                     if (cached && cached.el) {
13417                         ret = cached.el;
13418                         ret.dom = el;
13419                     } else {
13420                         ret = El.addToCache(new El(el));
13421                     }
13422                     return ret;
13423                 }
13424             }
13425
13426             return El.get(id);
13427         };
13428
13429     El.addToCache = function(el, id) {
13430         if (el) {
13431             id = id || el.id;
13432             EC[id] = {
13433                 el: el,
13434                 data: {},
13435                 events: {}
13436             };
13437         }
13438         return el;
13439     };
13440
13441     // private method for getting and setting element data
13442     El.data = function(el, key, value) {
13443         el = El.get(el);
13444         if (!el) {
13445             return null;
13446         }
13447         var c = EC[el.id].data;
13448         if (arguments.length == 2) {
13449             return c[key];
13450         } else {
13451             return (c[key] = value);
13452         }
13453     };
13454
13455     // private
13456     // Garbage collection - uncache elements/purge listeners on orphaned elements
13457     // so we don't hold a reference and cause the browser to retain them
13458     function garbageCollect() {
13459         if (!Ext.enableGarbageCollector) {
13460             clearInterval(El.collectorThreadId);
13461         } else {
13462             var eid,
13463             el,
13464             d,
13465             o;
13466
13467             for (eid in EC) {
13468                 if (!EC.hasOwnProperty(eid)) {
13469                     continue;
13470                 }
13471                 o = EC[eid];
13472                 if (o.skipGarbageCollection) {
13473                     continue;
13474                 }
13475                 el = o.el;
13476                 d = el.dom;
13477                 // -------------------------------------------------------
13478                 // Determining what is garbage:
13479                 // -------------------------------------------------------
13480                 // !d
13481                 // dom node is null, definitely garbage
13482                 // -------------------------------------------------------
13483                 // !d.parentNode
13484                 // no parentNode == direct orphan, definitely garbage
13485                 // -------------------------------------------------------
13486                 // !d.offsetParent && !document.getElementById(eid)
13487                 // display none elements have no offsetParent so we will
13488                 // also try to look it up by it's id. However, check
13489                 // offsetParent first so we don't do unneeded lookups.
13490                 // This enables collection of elements that are not orphans
13491                 // directly, but somewhere up the line they have an orphan
13492                 // parent.
13493                 // -------------------------------------------------------
13494                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
13495                     if (d && Ext.enableListenerCollection) {
13496                         Ext.EventManager.removeAll(d);
13497                     }
13498                     delete EC[eid];
13499                 }
13500             }
13501             // Cleanup IE Object leaks
13502             if (Ext.isIE) {
13503                 var t = {};
13504                 for (eid in EC) {
13505                     if (!EC.hasOwnProperty(eid)) {
13506                         continue;
13507                     }
13508                     t[eid] = EC[eid];
13509                 }
13510                 EC = Ext.cache = t;
13511             }
13512         }
13513     }
13514     El.collectorThreadId = setInterval(garbageCollect, 30000);
13515
13516     var flyFn = function() {};
13517     flyFn.prototype = El.prototype;
13518
13519     // dom is optional
13520     El.Flyweight = function(dom) {
13521         this.dom = dom;
13522     };
13523
13524     El.Flyweight.prototype = new flyFn();
13525     El.Flyweight.prototype.isFlyweight = true;
13526     El._flyweights = {};
13527
13528     /**
13529      * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
13530      * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
13531      * {@link Ext.Element#fly}.
13532      *
13533      * Use this to make one-time references to DOM elements which are not going to be accessed again either by
13534      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
13535      * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the Ext.Element
13536      * class.
13537      *
13538      * @param {String/HTMLElement} el The dom node or id
13539      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts (e.g.
13540      * internally Ext uses "_global")
13541      * @return {Ext.Element} The shared Element object (or null if no matching element was found)
13542      * @static
13543      */
13544     El.fly = function(el, named) {
13545         var ret = null;
13546         named = named || '_global';
13547         el = Ext.getDom(el);
13548         if (el) {
13549             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
13550             ret = El._flyweights[named];
13551         }
13552         return ret;
13553     };
13554
13555     /**
13556      * @member Ext
13557      * @method get
13558      * @alias Ext.Element#get
13559      */
13560     Ext.get = El.get;
13561
13562     /**
13563      * @member Ext
13564      * @method fly
13565      * @alias Ext.Element#fly
13566      */
13567     Ext.fly = El.fly;
13568
13569     // speedy lookup for elements never to box adjust
13570     var noBoxAdjust = Ext.isStrict ? {
13571         select: 1
13572     }: {
13573         input: 1,
13574         select: 1,
13575         textarea: 1
13576     };
13577     if (Ext.isIE || Ext.isGecko) {
13578         noBoxAdjust['button'] = 1;
13579     }
13580 })();
13581
13582 /**
13583  * @class Ext.Element
13584  */
13585 Ext.Element.addMethods({
13586     /**
13587      * 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)
13588      * @param {String} selector The simple selector to test
13589      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13590      * The max depth to search as a number or element (defaults to 50 || document.body)
13591      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
13592      * @return {HTMLElement} The matching DOM node (or null if no match was found)
13593      */
13594     findParent : function(simpleSelector, maxDepth, returnEl) {
13595         var p = this.dom,
13596             b = document.body,
13597             depth = 0,
13598             stopEl;
13599
13600         maxDepth = maxDepth || 50;
13601         if (isNaN(maxDepth)) {
13602             stopEl = Ext.getDom(maxDepth);
13603             maxDepth = Number.MAX_VALUE;
13604         }
13605         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
13606             if (Ext.DomQuery.is(p, simpleSelector)) {
13607                 return returnEl ? Ext.get(p) : p;
13608             }
13609             depth++;
13610             p = p.parentNode;
13611         }
13612         return null;
13613     },
13614
13615     /**
13616      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
13617      * @param {String} selector The simple selector to test
13618      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13619      * The max depth to search as a number or element (defaults to 10 || document.body)
13620      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
13621      * @return {HTMLElement} The matching DOM node (or null if no match was found)
13622      */
13623     findParentNode : function(simpleSelector, maxDepth, returnEl) {
13624         var p = Ext.fly(this.dom.parentNode, '_internal');
13625         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
13626     },
13627
13628     /**
13629      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
13630      * This is a shortcut for findParentNode() that always returns an Ext.Element.
13631      * @param {String} selector The simple selector to test
13632      * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
13633      * The max depth to search as a number or element (defaults to 10 || document.body)
13634      * @return {Ext.Element} The matching DOM node (or null if no match was found)
13635      */
13636     up : function(simpleSelector, maxDepth) {
13637         return this.findParentNode(simpleSelector, maxDepth, true);
13638     },
13639
13640     /**
13641      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
13642      * @param {String} selector The CSS selector
13643      * @return {Ext.CompositeElement/Ext.CompositeElement} The composite element
13644      */
13645     select : function(selector) {
13646         return Ext.Element.select(selector, false,  this.dom);
13647     },
13648
13649     /**
13650      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
13651      * @param {String} selector The CSS selector
13652      * @return {HTMLElement[]} An array of the matched nodes
13653      */
13654     query : function(selector) {
13655         return Ext.DomQuery.select(selector, this.dom);
13656     },
13657
13658     /**
13659      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
13660      * @param {String} selector The CSS selector
13661      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
13662      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
13663      */
13664     down : function(selector, returnDom) {
13665         var n = Ext.DomQuery.selectNode(selector, this.dom);
13666         return returnDom ? n : Ext.get(n);
13667     },
13668
13669     /**
13670      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
13671      * @param {String} selector The CSS selector
13672      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
13673      * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
13674      */
13675     child : function(selector, returnDom) {
13676         var node,
13677             me = this,
13678             id;
13679         id = Ext.get(me).id;
13680         // Escape . or :
13681         id = id.replace(/[\.:]/g, "\\$0");
13682         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
13683         return returnDom ? node : Ext.get(node);
13684     },
13685
13686      /**
13687      * Gets the parent node for this element, optionally chaining up trying to match a selector
13688      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
13689      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13690      * @return {Ext.Element/HTMLElement} The parent node or null
13691      */
13692     parent : function(selector, returnDom) {
13693         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
13694     },
13695
13696      /**
13697      * Gets the next sibling, skipping text nodes
13698      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
13699      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13700      * @return {Ext.Element/HTMLElement} The next sibling or null
13701      */
13702     next : function(selector, returnDom) {
13703         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
13704     },
13705
13706     /**
13707      * Gets the previous sibling, skipping text nodes
13708      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
13709      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
13710      * @return {Ext.Element/HTMLElement} The previous sibling or null
13711      */
13712     prev : function(selector, returnDom) {
13713         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
13714     },
13715
13716
13717     /**
13718      * Gets the first child, 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 first child or null
13722      */
13723     first : function(selector, returnDom) {
13724         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
13725     },
13726
13727     /**
13728      * Gets the last child, 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 last child or null
13732      */
13733     last : function(selector, returnDom) {
13734         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
13735     },
13736
13737     matchNode : function(dir, start, selector, returnDom) {
13738         if (!this.dom) {
13739             return null;
13740         }
13741
13742         var n = this.dom[start];
13743         while (n) {
13744             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
13745                 return !returnDom ? Ext.get(n) : n;
13746             }
13747             n = n[dir];
13748         }
13749         return null;
13750     }
13751 });
13752
13753 /**
13754  * @class Ext.Element
13755  */
13756 Ext.Element.addMethods({
13757     /**
13758      * Appends the passed element(s) to this element
13759      * @param {String/HTMLElement/Ext.Element} el
13760      * The id of the node, a DOM Node or an existing Element.
13761      * @return {Ext.Element} this
13762      */
13763     appendChild : function(el) {
13764         return Ext.get(el).appendTo(this);
13765     },
13766
13767     /**
13768      * Appends this element to the passed element
13769      * @param {String/HTMLElement/Ext.Element} el The new parent element.
13770      * The id of the node, a DOM Node or an existing Element.
13771      * @return {Ext.Element} this
13772      */
13773     appendTo : function(el) {
13774         Ext.getDom(el).appendChild(this.dom);
13775         return this;
13776     },
13777
13778     /**
13779      * Inserts this element before the passed element in the DOM
13780      * @param {String/HTMLElement/Ext.Element} el The element before which this element will be inserted.
13781      * The id of the node, a DOM Node or an existing Element.
13782      * @return {Ext.Element} this
13783      */
13784     insertBefore : function(el) {
13785         el = Ext.getDom(el);
13786         el.parentNode.insertBefore(this.dom, el);
13787         return this;
13788     },
13789
13790     /**
13791      * Inserts this element after the passed element in the DOM
13792      * @param {String/HTMLElement/Ext.Element} el The element to insert after.
13793      * The id of the node, a DOM Node or an existing Element.
13794      * @return {Ext.Element} this
13795      */
13796     insertAfter : function(el) {
13797         el = Ext.getDom(el);
13798         el.parentNode.insertBefore(this.dom, el.nextSibling);
13799         return this;
13800     },
13801
13802     /**
13803      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
13804      * @param {String/HTMLElement/Ext.Element/Object} el The id or element to insert or a DomHelper config
13805      * to create and insert
13806      * @return {Ext.Element} The new child
13807      */
13808     insertFirst : function(el, returnDom) {
13809         el = el || {};
13810         if (el.nodeType || el.dom || typeof el == 'string') { // element
13811             el = Ext.getDom(el);
13812             this.dom.insertBefore(el, this.dom.firstChild);
13813             return !returnDom ? Ext.get(el) : el;
13814         }
13815         else { // dh config
13816             return this.createChild(el, this.dom.firstChild, returnDom);
13817         }
13818     },
13819
13820     /**
13821      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
13822      * @param {String/HTMLElement/Ext.Element/Object/Array} el The id, element to insert or a DomHelper config
13823      * to create and insert *or* an array of any of those.
13824      * @param {String} where (optional) 'before' or 'after' defaults to before
13825      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.Element
13826      * @return {Ext.Element} The inserted Element. If an array is passed, the last inserted element is returned.
13827      */
13828     insertSibling: function(el, where, returnDom){
13829         var me = this, rt,
13830         isAfter = (where || 'before').toLowerCase() == 'after',
13831         insertEl;
13832
13833         if(Ext.isArray(el)){
13834             insertEl = me;
13835             Ext.each(el, function(e) {
13836                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
13837                 if(isAfter){
13838                     insertEl = rt;
13839                 }
13840             });
13841             return rt;
13842         }
13843
13844         el = el || {};
13845
13846         if(el.nodeType || el.dom){
13847             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
13848             if (!returnDom) {
13849                 rt = Ext.get(rt);
13850             }
13851         }else{
13852             if (isAfter && !me.dom.nextSibling) {
13853                 rt = Ext.DomHelper.append(me.dom.parentNode, el, !returnDom);
13854             } else {
13855                 rt = Ext.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
13856             }
13857         }
13858         return rt;
13859     },
13860
13861     /**
13862      * Replaces the passed element with this element
13863      * @param {String/HTMLElement/Ext.Element} el The element to replace.
13864      * The id of the node, a DOM Node or an existing Element.
13865      * @return {Ext.Element} this
13866      */
13867     replace : function(el) {
13868         el = Ext.get(el);
13869         this.insertBefore(el);
13870         el.remove();
13871         return this;
13872     },
13873     
13874     /**
13875      * Replaces this element with the passed element
13876      * @param {String/HTMLElement/Ext.Element/Object} el The new element (id of the node, a DOM Node
13877      * or an existing Element) or a DomHelper config of an element to create
13878      * @return {Ext.Element} this
13879      */
13880     replaceWith: function(el){
13881         var me = this;
13882             
13883         if(el.nodeType || el.dom || typeof el == 'string'){
13884             el = Ext.get(el);
13885             me.dom.parentNode.insertBefore(el, me.dom);
13886         }else{
13887             el = Ext.DomHelper.insertBefore(me.dom, el);
13888         }
13889         
13890         delete Ext.cache[me.id];
13891         Ext.removeNode(me.dom);      
13892         me.id = Ext.id(me.dom = el);
13893         Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);     
13894         return me;
13895     },
13896     
13897     /**
13898      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
13899      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
13900      * automatically generated with the specified attributes.
13901      * @param {HTMLElement} insertBefore (optional) a child element of this element
13902      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
13903      * @return {Ext.Element} The new child element
13904      */
13905     createChild : function(config, insertBefore, returnDom) {
13906         config = config || {tag:'div'};
13907         if (insertBefore) {
13908             return Ext.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
13909         }
13910         else {
13911             return Ext.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
13912         }
13913     },
13914
13915     /**
13916      * Creates and wraps this element with another element
13917      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
13918      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
13919      * @return {HTMLElement/Ext.Element} The newly created wrapper element
13920      */
13921     wrap : function(config, returnDom) {
13922         var newEl = Ext.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
13923             d = newEl.dom || newEl;
13924
13925         d.appendChild(this.dom);
13926         return newEl;
13927     },
13928
13929     /**
13930      * Inserts an html fragment into this element
13931      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
13932      * See {@link Ext.DomHelper#insertHtml} for details.
13933      * @param {String} html The HTML fragment
13934      * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
13935      * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
13936      */
13937     insertHtml : function(where, html, returnEl) {
13938         var el = Ext.DomHelper.insertHtml(where, this.dom, html);
13939         return returnEl ? Ext.get(el) : el;
13940     }
13941 });
13942
13943 /**
13944  * @class Ext.Element
13945  */
13946 (function(){
13947     // local style camelizing for speed
13948     var ELEMENT = Ext.Element,
13949         supports = Ext.supports,
13950         view = document.defaultView,
13951         opacityRe = /alpha\(opacity=(.*)\)/i,
13952         trimRe = /^\s+|\s+$/g,
13953         spacesRe = /\s+/,
13954         wordsRe = /\w/g,
13955         adjustDirect2DTableRe = /table-row|table-.*-group/,
13956         INTERNAL = '_internal',
13957         PADDING = 'padding',
13958         MARGIN = 'margin',
13959         BORDER = 'border',
13960         LEFT = '-left',
13961         RIGHT = '-right',
13962         TOP = '-top',
13963         BOTTOM = '-bottom',
13964         WIDTH = '-width',
13965         MATH = Math,
13966         HIDDEN = 'hidden',
13967         ISCLIPPED = 'isClipped',
13968         OVERFLOW = 'overflow',
13969         OVERFLOWX = 'overflow-x',
13970         OVERFLOWY = 'overflow-y',
13971         ORIGINALCLIP = 'originalClip',
13972         // special markup used throughout Ext when box wrapping elements
13973         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
13974         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
13975         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
13976         data = ELEMENT.data;
13977
13978     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>';
13979
13980     // These property values are read from the parentNode if they cannot be read
13981     // from the child:
13982     ELEMENT.inheritedProps = {
13983         fontSize: 1,
13984         fontStyle: 1,
13985         opacity: 1
13986     };
13987
13988     Ext.override(ELEMENT, {
13989
13990         /**
13991          * TODO: Look at this
13992          */
13993         // private  ==> used by Fx
13994         adjustWidth : function(width) {
13995             var me = this,
13996                 isNum = (typeof width == 'number');
13997
13998             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
13999                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14000             }
14001             return (isNum && width < 0) ? 0 : width;
14002         },
14003
14004         // private   ==> used by Fx
14005         adjustHeight : function(height) {
14006             var me = this,
14007                 isNum = (typeof height == "number");
14008
14009             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
14010                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14011             }
14012             return (isNum && height < 0) ? 0 : height;
14013         },
14014
14015
14016         /**
14017          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
14018          * @param {String/String[]} className The CSS classes to add separated by space, or an array of classes
14019          * @return {Ext.Element} this
14020          */
14021         addCls : function(className){
14022             var me = this,
14023                 cls = [],
14024                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
14025                 i, len, v;
14026             if (className === undefined) {
14027                 return me;
14028             }
14029             // Separate case is for speed
14030             if (Object.prototype.toString.call(className) !== '[object Array]') {
14031                 if (typeof className === 'string') {
14032                     className = className.replace(trimRe, '').split(spacesRe);
14033                     if (className.length === 1) {
14034                         className = className[0];
14035                         if (!me.hasCls(className)) {
14036                             me.dom.className += space + className;
14037                         }
14038                     } else {
14039                         this.addCls(className);
14040                     }
14041                 }
14042             } else {
14043                 for (i = 0, len = className.length; i < len; i++) {
14044                     v = className[i];
14045                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
14046                         cls.push(v);
14047                     }
14048                 }
14049                 if (cls.length) {
14050                     me.dom.className += space + cls.join(" ");
14051                 }
14052             }
14053             return me;
14054         },
14055
14056         /**
14057          * Removes one or more CSS classes from the element.
14058          * @param {String/String[]} className The CSS classes to remove separated by space, or an array of classes
14059          * @return {Ext.Element} this
14060          */
14061         removeCls : function(className){
14062             var me = this,
14063                 i, idx, len, cls, elClasses;
14064             if (className === undefined) {
14065                 return me;
14066             }
14067             if (Object.prototype.toString.call(className) !== '[object Array]') {
14068                 className = className.replace(trimRe, '').split(spacesRe);
14069             }
14070             if (me.dom && me.dom.className) {
14071                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
14072                 for (i = 0, len = className.length; i < len; i++) {
14073                     cls = className[i];
14074                     if (typeof cls == 'string') {
14075                         cls = cls.replace(trimRe, '');
14076                         idx = Ext.Array.indexOf(elClasses, cls);
14077                         if (idx != -1) {
14078                             Ext.Array.erase(elClasses, idx, 1);
14079                         }
14080                     }
14081                 }
14082                 me.dom.className = elClasses.join(" ");
14083             }
14084             return me;
14085         },
14086
14087         /**
14088          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
14089          * @param {String/String[]} className The CSS class to add, or an array of classes
14090          * @return {Ext.Element} this
14091          */
14092         radioCls : function(className){
14093             var cn = this.dom.parentNode.childNodes,
14094                 v, i, len;
14095             className = Ext.isArray(className) ? className : [className];
14096             for (i = 0, len = cn.length; i < len; i++) {
14097                 v = cn[i];
14098                 if (v && v.nodeType == 1) {
14099                     Ext.fly(v, '_internal').removeCls(className);
14100                 }
14101             }
14102             return this.addCls(className);
14103         },
14104
14105         /**
14106          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
14107          * @param {String} className The CSS class to toggle
14108          * @return {Ext.Element} this
14109          * @method
14110          */
14111         toggleCls : Ext.supports.ClassList ?
14112             function(className) {
14113                 this.dom.classList.toggle(Ext.String.trim(className));
14114                 return this;
14115             } :
14116             function(className) {
14117                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
14118             },
14119
14120         /**
14121          * Checks if the specified CSS class exists on this element's DOM node.
14122          * @param {String} className The CSS class to check for
14123          * @return {Boolean} True if the class exists, else false
14124          * @method
14125          */
14126         hasCls : Ext.supports.ClassList ?
14127             function(className) {
14128                 if (!className) {
14129                     return false;
14130                 }
14131                 className = className.split(spacesRe);
14132                 var ln = className.length,
14133                     i = 0;
14134                 for (; i < ln; i++) {
14135                     if (className[i] && this.dom.classList.contains(className[i])) {
14136                         return true;
14137                     }
14138                 }
14139                 return false;
14140             } :
14141             function(className){
14142                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
14143             },
14144
14145         /**
14146          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
14147          * @param {String} oldClassName The CSS class to replace
14148          * @param {String} newClassName The replacement CSS class
14149          * @return {Ext.Element} this
14150          */
14151         replaceCls : function(oldClassName, newClassName){
14152             return this.removeCls(oldClassName).addCls(newClassName);
14153         },
14154
14155         isStyle : function(style, val) {
14156             return this.getStyle(style) == val;
14157         },
14158
14159         /**
14160          * Normalizes currentStyle and computedStyle.
14161          * @param {String} property The style property whose value is returned.
14162          * @return {String} The current value of the style property for this element.
14163          * @method
14164          */
14165         getStyle : function() {
14166             return view && view.getComputedStyle ?
14167                 function(prop){
14168                     var el = this.dom,
14169                         v, cs, out, display, cleaner;
14170
14171                     if(el == document){
14172                         return null;
14173                     }
14174                     prop = ELEMENT.normalize(prop);
14175                     out = (v = el.style[prop]) ? v :
14176                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
14177
14178                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
14179                     // numbers larger.
14180                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
14181                         cleaner = ELEMENT.getRightMarginFixCleaner(el);
14182                         display = this.getStyle('display');
14183                         el.style.display = 'inline-block';
14184                         out = view.getComputedStyle(el, '').marginRight;
14185                         el.style.display = display;
14186                         cleaner();
14187                     }
14188
14189                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
14190                         out = 'transparent';
14191                     }
14192                     return out;
14193                 } :
14194                 function (prop) {
14195                     var el = this.dom,
14196                         m, cs;
14197
14198                     if (el == document) {
14199                         return null;
14200                     }
14201                     prop = ELEMENT.normalize(prop);
14202
14203                     do {
14204                         if (prop == 'opacity') {
14205                             if (el.style.filter.match) {
14206                                 m = el.style.filter.match(opacityRe);
14207                                 if(m){
14208                                     var fv = parseFloat(m[1]);
14209                                     if(!isNaN(fv)){
14210                                         return fv ? fv / 100 : 0;
14211                                     }
14212                                 }
14213                             }
14214                             return 1;
14215                         }
14216
14217                         // the try statement does have a cost, so we avoid it unless we are
14218                         // on IE6
14219                         if (!Ext.isIE6) {
14220                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14221                         }
14222
14223                         try {
14224                             return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
14225                         } catch (e) {
14226                             // in some cases, IE6 will throw Invalid Argument for properties
14227                             // like fontSize (see in /examples/tabs/tabs.html).
14228                         }
14229
14230                         if (!ELEMENT.inheritedProps[prop]) {
14231                             break;
14232                         }
14233
14234                         el = el.parentNode;
14235                         // this is _not_ perfect, but we can only hope that the style we
14236                         // need is inherited from a parentNode. If not and since IE won't
14237                         // give us the info we need, we are never going to be 100% right.
14238                     } while (el);
14239
14240                     return null;
14241                 }
14242         }(),
14243
14244         /**
14245          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
14246          * are convert to standard 6 digit hex color.
14247          * @param {String} attr The css attribute
14248          * @param {String} defaultValue The default value to use when a valid color isn't found
14249          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
14250          * color anims.
14251          */
14252         getColor : function(attr, defaultValue, prefix){
14253             var v = this.getStyle(attr),
14254                 color = prefix || prefix === '' ? prefix : '#',
14255                 h;
14256
14257             if(!v || (/transparent|inherit/.test(v))) {
14258                 return defaultValue;
14259             }
14260             if(/^r/.test(v)){
14261                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
14262                     h = parseInt(s, 10);
14263                     color += (h < 16 ? '0' : '') + h.toString(16);
14264                 });
14265             }else{
14266                 v = v.replace('#', '');
14267                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
14268             }
14269             return(color.length > 5 ? color.toLowerCase() : defaultValue);
14270         },
14271
14272         /**
14273          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
14274          * @param {String/Object} property The style property to be set, or an object of multiple styles.
14275          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
14276          * @return {Ext.Element} this
14277          */
14278         setStyle : function(prop, value){
14279             var me = this,
14280                 tmp, style;
14281
14282             if (!me.dom) {
14283                 return me;
14284             }
14285             if (typeof prop === 'string') {
14286                 tmp = {};
14287                 tmp[prop] = value;
14288                 prop = tmp;
14289             }
14290             for (style in prop) {
14291                 if (prop.hasOwnProperty(style)) {
14292                     value = Ext.value(prop[style], '');
14293                     if (style == 'opacity') {
14294                         me.setOpacity(value);
14295                     }
14296                     else {
14297                         me.dom.style[ELEMENT.normalize(style)] = value;
14298                     }
14299                 }
14300             }
14301             return me;
14302         },
14303
14304         /**
14305          * Set the opacity of the element
14306          * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
14307          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
14308          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
14309          * @return {Ext.Element} this
14310          */
14311         setOpacity: function(opacity, animate) {
14312             var me = this,
14313                 dom = me.dom,
14314                 val,
14315                 style;
14316
14317             if (!me.dom) {
14318                 return me;
14319             }
14320
14321             style = me.dom.style;
14322
14323             if (!animate || !me.anim) {
14324                 if (!Ext.supports.Opacity) {
14325                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
14326                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
14327
14328                     style.zoom = 1;
14329                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
14330                 }
14331                 else {
14332                     style.opacity = opacity;
14333                 }
14334             }
14335             else {
14336                 if (!Ext.isObject(animate)) {
14337                     animate = {
14338                         duration: 350,
14339                         easing: 'ease-in'
14340                     };
14341                 }
14342                 me.animate(Ext.applyIf({
14343                     to: {
14344                         opacity: opacity
14345                     }
14346                 },
14347                 animate));
14348             }
14349             return me;
14350         },
14351
14352
14353         /**
14354          * Clears any opacity settings from this element. Required in some cases for IE.
14355          * @return {Ext.Element} this
14356          */
14357         clearOpacity : function(){
14358             var style = this.dom.style;
14359             if(!Ext.supports.Opacity){
14360                 if(!Ext.isEmpty(style.filter)){
14361                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
14362                 }
14363             }else{
14364                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
14365             }
14366             return this;
14367         },
14368
14369         /**
14370          * @private
14371          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
14372          * @return {Number} 0 or 1
14373          */
14374         adjustDirect2DDimension: function(dimension) {
14375             var me = this,
14376                 dom = me.dom,
14377                 display = me.getStyle('display'),
14378                 inlineDisplay = dom.style['display'],
14379                 inlinePosition = dom.style['position'],
14380                 originIndex = dimension === 'width' ? 0 : 1,
14381                 floating;
14382
14383             if (display === 'inline') {
14384                 dom.style['display'] = 'inline-block';
14385             }
14386
14387             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
14388
14389             // floating will contain digits that appears after the decimal point
14390             // if height or width are set to auto we fallback to msTransformOrigin calculation
14391             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
14392
14393             dom.style['position'] = inlinePosition;
14394
14395             if (display === 'inline') {
14396                 dom.style['display'] = inlineDisplay;
14397             }
14398
14399             return floating;
14400         },
14401
14402         /**
14403          * Returns the offset height of the element
14404          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
14405          * @return {Number} The element's height
14406          */
14407         getHeight: function(contentHeight, preciseHeight) {
14408             var me = this,
14409                 dom = me.dom,
14410                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14411                 height, overflow, style, floating;
14412
14413             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14414             // We will put the overflow back to it's original value when we are done measuring.
14415             if (Ext.isIEQuirks) {
14416                 style = dom.style;
14417                 overflow = style.overflow;
14418                 me.setStyle({ overflow: 'hidden'});
14419             }
14420
14421             height = dom.offsetHeight;
14422
14423             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
14424
14425             // IE9 Direct2D dimension rounding bug
14426             if (!hidden && Ext.supports.Direct2DBug) {
14427                 floating = me.adjustDirect2DDimension('height');
14428                 if (preciseHeight) {
14429                     height += floating;
14430                 }
14431                 else if (floating > 0 && floating < 0.5) {
14432                     height++;
14433                 }
14434             }
14435
14436             if (contentHeight) {
14437                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
14438             }
14439
14440             if (Ext.isIEQuirks) {
14441                 me.setStyle({ overflow: overflow});
14442             }
14443
14444             if (height < 0) {
14445                 height = 0;
14446             }
14447             return height;
14448         },
14449
14450         /**
14451          * Returns the offset width of the element
14452          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
14453          * @return {Number} The element's width
14454          */
14455         getWidth: function(contentWidth, preciseWidth) {
14456             var me = this,
14457                 dom = me.dom,
14458                 hidden = Ext.isIE && me.isStyle('display', 'none'),
14459                 rect, width, overflow, style, floating, parentPosition;
14460
14461             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14462             // We will put the overflow back to it's original value when we are done measuring.
14463             if (Ext.isIEQuirks) {
14464                 style = dom.style;
14465                 overflow = style.overflow;
14466                 me.setStyle({overflow: 'hidden'});
14467             }
14468
14469             // Fix Opera 10.5x width calculation issues
14470             if (Ext.isOpera10_5) {
14471                 if (dom.parentNode.currentStyle.position === 'relative') {
14472                     parentPosition = dom.parentNode.style.position;
14473                     dom.parentNode.style.position = 'static';
14474                     width = dom.offsetWidth;
14475                     dom.parentNode.style.position = parentPosition;
14476                 }
14477                 width = Math.max(width || 0, dom.offsetWidth);
14478
14479             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
14480             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
14481             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
14482             // subpixel measurements so we can force them to always be rounded up. See
14483             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
14484             } else if (Ext.supports.BoundingClientRect) {
14485                 rect = dom.getBoundingClientRect();
14486                 width = rect.right - rect.left;
14487                 width = preciseWidth ? width : Math.ceil(width);
14488             } else {
14489                 width = dom.offsetWidth;
14490             }
14491
14492             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
14493
14494             // IE9 Direct2D dimension rounding bug
14495             if (!hidden && Ext.supports.Direct2DBug) {
14496                 floating = me.adjustDirect2DDimension('width');
14497                 if (preciseWidth) {
14498                     width += floating;
14499                 }
14500                 else if (floating > 0 && floating < 0.5) {
14501                     width++;
14502                 }
14503             }
14504
14505             if (contentWidth) {
14506                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
14507             }
14508
14509             if (Ext.isIEQuirks) {
14510                 me.setStyle({ overflow: overflow});
14511             }
14512
14513             if (width < 0) {
14514                 width = 0;
14515             }
14516             return width;
14517         },
14518
14519         /**
14520          * Set the width of this Element.
14521          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
14522          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14523          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14524          * </ul></div>
14525          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14526          * @return {Ext.Element} this
14527          */
14528         setWidth : function(width, animate){
14529             var me = this;
14530             width = me.adjustWidth(width);
14531             if (!animate || !me.anim) {
14532                 me.dom.style.width = me.addUnits(width);
14533             }
14534             else {
14535                 if (!Ext.isObject(animate)) {
14536                     animate = {};
14537                 }
14538                 me.animate(Ext.applyIf({
14539                     to: {
14540                         width: width
14541                     }
14542                 }, animate));
14543             }
14544             return me;
14545         },
14546
14547         /**
14548          * Set the height of this Element.
14549          * <pre><code>
14550 // change the height to 200px and animate with default configuration
14551 Ext.fly('elementId').setHeight(200, true);
14552
14553 // change the height to 150px and animate with a custom configuration
14554 Ext.fly('elId').setHeight(150, {
14555     duration : .5, // animation will have a duration of .5 seconds
14556     // will change the content to "finished"
14557     callback: function(){ this.{@link #update}("finished"); }
14558 });
14559          * </code></pre>
14560          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
14561          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
14562          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
14563          * </ul></div>
14564          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14565          * @return {Ext.Element} this
14566          */
14567          setHeight : function(height, animate){
14568             var me = this;
14569             height = me.adjustHeight(height);
14570             if (!animate || !me.anim) {
14571                 me.dom.style.height = me.addUnits(height);
14572             }
14573             else {
14574                 if (!Ext.isObject(animate)) {
14575                     animate = {};
14576                 }
14577                 me.animate(Ext.applyIf({
14578                     to: {
14579                         height: height
14580                     }
14581                 }, animate));
14582             }
14583             return me;
14584         },
14585
14586         /**
14587          * Gets the width of the border(s) for the specified side(s)
14588          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
14589          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
14590          * @return {Number} The width of the sides passed added together
14591          */
14592         getBorderWidth : function(side){
14593             return this.addStyles(side, borders);
14594         },
14595
14596         /**
14597          * Gets the width of the padding(s) for the specified side(s)
14598          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
14599          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
14600          * @return {Number} The padding of the sides passed added together
14601          */
14602         getPadding : function(side){
14603             return this.addStyles(side, paddings);
14604         },
14605
14606         /**
14607          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
14608          * @return {Ext.Element} this
14609          */
14610         clip : function(){
14611             var me = this,
14612                 dom = me.dom;
14613
14614             if(!data(dom, ISCLIPPED)){
14615                 data(dom, ISCLIPPED, true);
14616                 data(dom, ORIGINALCLIP, {
14617                     o: me.getStyle(OVERFLOW),
14618                     x: me.getStyle(OVERFLOWX),
14619                     y: me.getStyle(OVERFLOWY)
14620                 });
14621                 me.setStyle(OVERFLOW, HIDDEN);
14622                 me.setStyle(OVERFLOWX, HIDDEN);
14623                 me.setStyle(OVERFLOWY, HIDDEN);
14624             }
14625             return me;
14626         },
14627
14628         /**
14629          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
14630          * @return {Ext.Element} this
14631          */
14632         unclip : function(){
14633             var me = this,
14634                 dom = me.dom,
14635                 clip;
14636
14637             if(data(dom, ISCLIPPED)){
14638                 data(dom, ISCLIPPED, false);
14639                 clip = data(dom, ORIGINALCLIP);
14640                 if(clip.o){
14641                     me.setStyle(OVERFLOW, clip.o);
14642                 }
14643                 if(clip.x){
14644                     me.setStyle(OVERFLOWX, clip.x);
14645                 }
14646                 if(clip.y){
14647                     me.setStyle(OVERFLOWY, clip.y);
14648                 }
14649             }
14650             return me;
14651         },
14652
14653         // private
14654         addStyles : function(sides, styles){
14655             var totalSize = 0,
14656                 sidesArr = sides.match(wordsRe),
14657                 i = 0,
14658                 len = sidesArr.length,
14659                 side, size;
14660             for (; i < len; i++) {
14661                 side = sidesArr[i];
14662                 size = side && parseInt(this.getStyle(styles[side]), 10);
14663                 if (size) {
14664                     totalSize += MATH.abs(size);
14665                 }
14666             }
14667             return totalSize;
14668         },
14669
14670         margins : margins,
14671
14672         /**
14673          * More flexible version of {@link #setStyle} for setting style properties.
14674          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
14675          * a function which returns such a specification.
14676          * @return {Ext.Element} this
14677          */
14678         applyStyles : function(style){
14679             Ext.DomHelper.applyStyles(this.dom, style);
14680             return this;
14681         },
14682
14683         /**
14684          * Returns an object with properties matching the styles requested.
14685          * For example, el.getStyles('color', 'font-size', 'width') might return
14686          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
14687          * @param {String} style1 A style name
14688          * @param {String} style2 A style name
14689          * @param {String} etc.
14690          * @return {Object} The style object
14691          */
14692         getStyles : function(){
14693             var styles = {},
14694                 len = arguments.length,
14695                 i = 0, style;
14696
14697             for(; i < len; ++i) {
14698                 style = arguments[i];
14699                 styles[style] = this.getStyle(style);
14700             }
14701             return styles;
14702         },
14703
14704        /**
14705         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
14706         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
14707         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
14708         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
14709         * is of this form:</p>
14710         * <pre><code>
14711     Ext.Element.boxMarkup =
14712     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
14713      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
14714      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
14715         * </code></pre>
14716         * <p>Example usage:</p>
14717         * <pre><code>
14718     // Basic box wrap
14719     Ext.get("foo").boxWrap();
14720
14721     // You can also add a custom class and use CSS inheritance rules to customize the box look.
14722     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
14723     // for how to create a custom box wrap style.
14724     Ext.get("foo").boxWrap().addCls("x-box-blue");
14725         * </code></pre>
14726         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
14727         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
14728         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
14729         * also supply all of the necessary rules.
14730         * @return {Ext.Element} The outermost wrapping element of the created box structure.
14731         */
14732         boxWrap : function(cls){
14733             cls = cls || Ext.baseCSSPrefix + 'box';
14734             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(ELEMENT.boxMarkup, cls) + "</div>"));
14735             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
14736             return el;
14737         },
14738
14739         /**
14740          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
14741          * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
14742          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14743          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14744          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
14745          * </ul></div>
14746          * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
14747          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14748          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
14749          * </ul></div>
14750          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14751          * @return {Ext.Element} this
14752          */
14753         setSize : function(width, height, animate){
14754             var me = this;
14755             if (Ext.isObject(width)) { // in case of object from getSize()
14756                 animate = height;
14757                 height = width.height;
14758                 width = width.width;
14759             }
14760             width = me.adjustWidth(width);
14761             height = me.adjustHeight(height);
14762             if(!animate || !me.anim){
14763                 // Must touch some property before setting style.width/height on non-quirk IE6,7, or the
14764                 // properties will not reflect the changes on the style immediately
14765                 if (!Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7)) {
14766                     me.dom.offsetTop;
14767                 }
14768                 me.dom.style.width = me.addUnits(width);
14769                 me.dom.style.height = me.addUnits(height);
14770             }
14771             else {
14772                 if (animate === true) {
14773                     animate = {};
14774                 }
14775                 me.animate(Ext.applyIf({
14776                     to: {
14777                         width: width,
14778                         height: height
14779                     }
14780                 }, animate));
14781             }
14782             return me;
14783         },
14784
14785         /**
14786          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
14787          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
14788          * if a height has not been set using CSS.
14789          * @return {Number}
14790          */
14791         getComputedHeight : function(){
14792             var me = this,
14793                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
14794             if(!h){
14795                 h = parseFloat(me.getStyle('height')) || 0;
14796                 if(!me.isBorderBox()){
14797                     h += me.getFrameWidth('tb');
14798                 }
14799             }
14800             return h;
14801         },
14802
14803         /**
14804          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
14805          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
14806          * if a width has not been set using CSS.
14807          * @return {Number}
14808          */
14809         getComputedWidth : function(){
14810             var me = this,
14811                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
14812
14813             if(!w){
14814                 w = parseFloat(me.getStyle('width')) || 0;
14815                 if(!me.isBorderBox()){
14816                     w += me.getFrameWidth('lr');
14817                 }
14818             }
14819             return w;
14820         },
14821
14822         /**
14823          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
14824          for more information about the sides.
14825          * @param {String} sides
14826          * @return {Number}
14827          */
14828         getFrameWidth : function(sides, onlyContentBox){
14829             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
14830         },
14831
14832         /**
14833          * Sets up event handlers to add and remove a css class when the mouse is over this element
14834          * @param {String} className
14835          * @return {Ext.Element} this
14836          */
14837         addClsOnOver : function(className){
14838             var dom = this.dom;
14839             this.hover(
14840                 function(){
14841                     Ext.fly(dom, INTERNAL).addCls(className);
14842                 },
14843                 function(){
14844                     Ext.fly(dom, INTERNAL).removeCls(className);
14845                 }
14846             );
14847             return this;
14848         },
14849
14850         /**
14851          * Sets up event handlers to add and remove a css class when this element has the focus
14852          * @param {String} className
14853          * @return {Ext.Element} this
14854          */
14855         addClsOnFocus : function(className){
14856             var me = this,
14857                 dom = me.dom;
14858             me.on("focus", function(){
14859                 Ext.fly(dom, INTERNAL).addCls(className);
14860             });
14861             me.on("blur", function(){
14862                 Ext.fly(dom, INTERNAL).removeCls(className);
14863             });
14864             return me;
14865         },
14866
14867         /**
14868          * 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)
14869          * @param {String} className
14870          * @return {Ext.Element} this
14871          */
14872         addClsOnClick : function(className){
14873             var dom = this.dom;
14874             this.on("mousedown", function(){
14875                 Ext.fly(dom, INTERNAL).addCls(className);
14876                 var d = Ext.getDoc(),
14877                     fn = function(){
14878                         Ext.fly(dom, INTERNAL).removeCls(className);
14879                         d.removeListener("mouseup", fn);
14880                     };
14881                 d.on("mouseup", fn);
14882             });
14883             return this;
14884         },
14885
14886         /**
14887          * <p>Returns the dimensions of the element available to lay content out in.<p>
14888          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
14889          * example:<pre><code>
14890         var vpSize = Ext.getBody().getViewSize();
14891
14892         // all Windows created afterwards will have a default value of 90% height and 95% width
14893         Ext.Window.override({
14894             width: vpSize.width * 0.9,
14895             height: vpSize.height * 0.95
14896         });
14897         // To handle window resizing you would have to hook onto onWindowResize.
14898         * </code></pre>
14899         *
14900         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
14901         * To obtain the size including scrollbars, use getStyleSize
14902         *
14903         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14904         */
14905
14906         getViewSize : function(){
14907             var me = this,
14908                 dom = me.dom,
14909                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
14910                 style, overflow, ret;
14911
14912             // If the body, use static methods
14913             if (isDoc) {
14914                 ret = {
14915                     width : ELEMENT.getViewWidth(),
14916                     height : ELEMENT.getViewHeight()
14917                 };
14918
14919             // Else use clientHeight/clientWidth
14920             }
14921             else {
14922                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14923                 // We will put the overflow back to it's original value when we are done measuring.
14924                 if (Ext.isIE6 || Ext.isIEQuirks) {
14925                     style = dom.style;
14926                     overflow = style.overflow;
14927                     me.setStyle({ overflow: 'hidden'});
14928                 }
14929                 ret = {
14930                     width : dom.clientWidth,
14931                     height : dom.clientHeight
14932                 };
14933                 if (Ext.isIE6 || Ext.isIEQuirks) {
14934                     me.setStyle({ overflow: overflow });
14935                 }
14936             }
14937             return ret;
14938         },
14939
14940         /**
14941         * <p>Returns the dimensions of the element available to lay content out in.<p>
14942         *
14943         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
14944         * To obtain the size excluding scrollbars, use getViewSize
14945         *
14946         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14947         */
14948
14949         getStyleSize : function(){
14950             var me = this,
14951                 doc = document,
14952                 d = this.dom,
14953                 isDoc = (d == doc || d == doc.body),
14954                 s = d.style,
14955                 w, h;
14956
14957             // If the body, use static methods
14958             if (isDoc) {
14959                 return {
14960                     width : ELEMENT.getViewWidth(),
14961                     height : ELEMENT.getViewHeight()
14962                 };
14963             }
14964             // Use Styles if they are set
14965             if(s.width && s.width != 'auto'){
14966                 w = parseFloat(s.width);
14967                 if(me.isBorderBox()){
14968                    w -= me.getFrameWidth('lr');
14969                 }
14970             }
14971             // Use Styles if they are set
14972             if(s.height && s.height != 'auto'){
14973                 h = parseFloat(s.height);
14974                 if(me.isBorderBox()){
14975                    h -= me.getFrameWidth('tb');
14976                 }
14977             }
14978             // Use getWidth/getHeight if style not set.
14979             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
14980         },
14981
14982         /**
14983          * Returns the size of the element.
14984          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
14985          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
14986          */
14987         getSize : function(contentSize){
14988             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
14989         },
14990
14991         /**
14992          * Forces the browser to repaint this element
14993          * @return {Ext.Element} this
14994          */
14995         repaint : function(){
14996             var dom = this.dom;
14997             this.addCls(Ext.baseCSSPrefix + 'repaint');
14998             setTimeout(function(){
14999                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
15000             }, 1);
15001             return this;
15002         },
15003
15004         /**
15005          * Enable text selection for this element (normalized across browsers)
15006          * @return {Ext.Element} this
15007          */
15008         selectable : function() {
15009             var me = this;
15010             me.dom.unselectable = "off";
15011             // Prevent it from bubles up and enables it to be selectable
15012             me.on('selectstart', function (e) {
15013                 e.stopPropagation();
15014                 return true;
15015             });
15016             me.applyStyles("-moz-user-select: text; -khtml-user-select: text;");
15017             me.removeCls(Ext.baseCSSPrefix + 'unselectable');
15018             return me;
15019         },
15020
15021         /**
15022          * Disables text selection for this element (normalized across browsers)
15023          * @return {Ext.Element} this
15024          */
15025         unselectable : function(){
15026             var me = this;
15027             me.dom.unselectable = "on";
15028
15029             me.swallowEvent("selectstart", true);
15030             me.applyStyles("-moz-user-select:-moz-none;-khtml-user-select:none;");
15031             me.addCls(Ext.baseCSSPrefix + 'unselectable');
15032
15033             return me;
15034         },
15035
15036         /**
15037          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
15038          * then it returns the calculated width of the sides (see getPadding)
15039          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
15040          * @return {Object/Number}
15041          */
15042         getMargin : function(side){
15043             var me = this,
15044                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
15045                 o = {},
15046                 key;
15047
15048             if (!side) {
15049                 for (key in me.margins){
15050                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
15051                 }
15052                 return o;
15053             } else {
15054                 return me.addStyles.call(me, side, me.margins);
15055             }
15056         }
15057     });
15058 })();
15059 /**
15060  * @class Ext.Element
15061  */
15062 /**
15063  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
15064  * @static
15065  * @type Number
15066  */
15067 Ext.Element.VISIBILITY = 1;
15068 /**
15069  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
15070  * @static
15071  * @type Number
15072  */
15073 Ext.Element.DISPLAY = 2;
15074
15075 /**
15076  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
15077  * to hide element.
15078  * @static
15079  * @type Number
15080  */
15081 Ext.Element.OFFSETS = 3;
15082
15083
15084 Ext.Element.ASCLASS = 4;
15085
15086 /**
15087  * Defaults to 'x-hide-nosize'
15088  * @static
15089  * @type String
15090  */
15091 Ext.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
15092
15093 Ext.Element.addMethods(function(){
15094     var El = Ext.Element,
15095         OPACITY = "opacity",
15096         VISIBILITY = "visibility",
15097         DISPLAY = "display",
15098         HIDDEN = "hidden",
15099         OFFSETS = "offsets",
15100         ASCLASS = "asclass",
15101         NONE = "none",
15102         NOSIZE = 'nosize',
15103         ORIGINALDISPLAY = 'originalDisplay',
15104         VISMODE = 'visibilityMode',
15105         ISVISIBLE = 'isVisible',
15106         data = El.data,
15107         getDisplay = function(dom){
15108             var d = data(dom, ORIGINALDISPLAY);
15109             if(d === undefined){
15110                 data(dom, ORIGINALDISPLAY, d = '');
15111             }
15112             return d;
15113         },
15114         getVisMode = function(dom){
15115             var m = data(dom, VISMODE);
15116             if(m === undefined){
15117                 data(dom, VISMODE, m = 1);
15118             }
15119             return m;
15120         };
15121
15122     return {
15123         /**
15124          * @property {String} originalDisplay
15125          * The element's default display mode
15126          */
15127         originalDisplay : "",
15128         visibilityMode : 1,
15129
15130         /**
15131          * Sets the element's visibility mode. When setVisible() is called it
15132          * will use this to determine whether to set the visibility or the display property.
15133          * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
15134          * @return {Ext.Element} this
15135          */
15136         setVisibilityMode : function(visMode){
15137             data(this.dom, VISMODE, visMode);
15138             return this;
15139         },
15140
15141         /**
15142          * Checks whether the element is currently visible using both visibility and display properties.
15143          * @return {Boolean} True if the element is currently visible, else false
15144          */
15145         isVisible : function() {
15146             var me = this,
15147                 dom = me.dom,
15148                 visible = data(dom, ISVISIBLE);
15149
15150             if(typeof visible == 'boolean'){ //return the cached value if registered
15151                 return visible;
15152             }
15153             //Determine the current state based on display states
15154             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
15155                       !me.isStyle(DISPLAY, NONE) &&
15156                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
15157
15158             data(dom, ISVISIBLE, visible);
15159             return visible;
15160         },
15161
15162         /**
15163          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
15164          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
15165          * @param {Boolean} visible Whether the element is visible
15166          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15167          * @return {Ext.Element} this
15168          */
15169         setVisible : function(visible, animate){
15170             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
15171                 dom = me.dom,
15172                 visMode = getVisMode(dom);
15173
15174
15175             // hideMode string override
15176             if (typeof animate == 'string'){
15177                 switch (animate) {
15178                     case DISPLAY:
15179                         visMode = El.DISPLAY;
15180                         break;
15181                     case VISIBILITY:
15182                         visMode = El.VISIBILITY;
15183                         break;
15184                     case OFFSETS:
15185                         visMode = El.OFFSETS;
15186                         break;
15187                     case NOSIZE:
15188                     case ASCLASS:
15189                         visMode = El.ASCLASS;
15190                         break;
15191                 }
15192                 me.setVisibilityMode(visMode);
15193                 animate = false;
15194             }
15195
15196             if (!animate || !me.anim) {
15197                 if(visMode == El.ASCLASS ){
15198
15199                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
15200
15201                 } else if (visMode == El.DISPLAY){
15202
15203                     return me.setDisplayed(visible);
15204
15205                 } else if (visMode == El.OFFSETS){
15206
15207                     if (!visible){
15208                         // Remember position for restoring, if we are not already hidden by offsets.
15209                         if (!me.hideModeStyles) {
15210                             me.hideModeStyles = {
15211                                 position: me.getStyle('position'),
15212                                 top: me.getStyle('top'),
15213                                 left: me.getStyle('left')
15214                             };
15215                         }
15216                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
15217                     }
15218
15219                     // Only "restore" as position if we have actually been hidden using offsets.
15220                     // Calling setVisible(true) on a positioned element should not reposition it.
15221                     else if (me.hideModeStyles) {
15222                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
15223                         delete me.hideModeStyles;
15224                     }
15225
15226                 }else{
15227                     me.fixDisplay();
15228                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
15229                     dom.style.visibility = visible ? '' : HIDDEN;
15230                 }
15231             }else{
15232                 // closure for composites
15233                 if(visible){
15234                     me.setOpacity(0.01);
15235                     me.setVisible(true);
15236                 }
15237                 if (!Ext.isObject(animate)) {
15238                     animate = {
15239                         duration: 350,
15240                         easing: 'ease-in'
15241                     };
15242                 }
15243                 me.animate(Ext.applyIf({
15244                     callback: function() {
15245                         visible || me.setVisible(false).setOpacity(1);
15246                     },
15247                     to: {
15248                         opacity: (visible) ? 1 : 0
15249                     }
15250                 }, animate));
15251             }
15252             data(dom, ISVISIBLE, visible);  //set logical visibility state
15253             return me;
15254         },
15255
15256
15257         /**
15258          * @private
15259          * Determine if the Element has a relevant height and width available based
15260          * upon current logical visibility state
15261          */
15262         hasMetrics  : function(){
15263             var dom = this.dom;
15264             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
15265         },
15266
15267         /**
15268          * Toggles the element's visibility or display, depending on visibility mode.
15269          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
15270          * @return {Ext.Element} this
15271          */
15272         toggle : function(animate){
15273             var me = this;
15274             me.setVisible(!me.isVisible(), me.anim(animate));
15275             return me;
15276         },
15277
15278         /**
15279          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
15280          * @param {Boolean/String} value Boolean value to display the element using its default display, or a string to set the display directly.
15281          * @return {Ext.Element} this
15282          */
15283         setDisplayed : function(value) {
15284             if(typeof value == "boolean"){
15285                value = value ? getDisplay(this.dom) : NONE;
15286             }
15287             this.setStyle(DISPLAY, value);
15288             return this;
15289         },
15290
15291         // private
15292         fixDisplay : function(){
15293             var me = this;
15294             if (me.isStyle(DISPLAY, NONE)) {
15295                 me.setStyle(VISIBILITY, HIDDEN);
15296                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
15297                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
15298                     me.setStyle(DISPLAY, "block");
15299                 }
15300             }
15301         },
15302
15303         /**
15304          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15305          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15306          * @return {Ext.Element} this
15307          */
15308         hide : function(animate){
15309             // hideMode override
15310             if (typeof animate == 'string'){
15311                 this.setVisible(false, animate);
15312                 return this;
15313             }
15314             this.setVisible(false, this.anim(animate));
15315             return this;
15316         },
15317
15318         /**
15319         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
15320         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
15321          * @return {Ext.Element} this
15322          */
15323         show : function(animate){
15324             // hideMode override
15325             if (typeof animate == 'string'){
15326                 this.setVisible(true, animate);
15327                 return this;
15328             }
15329             this.setVisible(true, this.anim(animate));
15330             return this;
15331         }
15332     };
15333 }());
15334 /**
15335  * @class Ext.Element
15336  */
15337 Ext.applyIf(Ext.Element.prototype, {
15338     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15339     animate: function(config) {
15340         var me = this;
15341         if (!me.id) {
15342             me = Ext.get(me.dom);
15343         }
15344         if (Ext.fx.Manager.hasFxBlock(me.id)) {
15345             return me;
15346         }
15347         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
15348         return this;
15349     },
15350
15351     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
15352     anim: function(config) {
15353         if (!Ext.isObject(config)) {
15354             return (config) ? {} : false;
15355         }
15356
15357         var me = this,
15358             duration = config.duration || Ext.fx.Anim.prototype.duration,
15359             easing = config.easing || 'ease',
15360             animConfig;
15361
15362         if (config.stopAnimation) {
15363             me.stopAnimation();
15364         }
15365
15366         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
15367
15368         // Clear any 'paused' defaults.
15369         Ext.fx.Manager.setFxDefaults(me.id, {
15370             delay: 0
15371         });
15372
15373         animConfig = {
15374             target: me,
15375             remove: config.remove,
15376             alternate: config.alternate || false,
15377             duration: duration,
15378             easing: easing,
15379             callback: config.callback,
15380             listeners: config.listeners,
15381             iterations: config.iterations || 1,
15382             scope: config.scope,
15383             block: config.block,
15384             concurrent: config.concurrent,
15385             delay: config.delay || 0,
15386             paused: true,
15387             keyframes: config.keyframes,
15388             from: config.from || {},
15389             to: Ext.apply({}, config)
15390         };
15391         Ext.apply(animConfig.to, config.to);
15392
15393         // Anim API properties - backward compat
15394         delete animConfig.to.to;
15395         delete animConfig.to.from;
15396         delete animConfig.to.remove;
15397         delete animConfig.to.alternate;
15398         delete animConfig.to.keyframes;
15399         delete animConfig.to.iterations;
15400         delete animConfig.to.listeners;
15401         delete animConfig.to.target;
15402         delete animConfig.to.paused;
15403         delete animConfig.to.callback;
15404         delete animConfig.to.scope;
15405         delete animConfig.to.duration;
15406         delete animConfig.to.easing;
15407         delete animConfig.to.concurrent;
15408         delete animConfig.to.block;
15409         delete animConfig.to.stopAnimation;
15410         delete animConfig.to.delay;
15411         return animConfig;
15412     },
15413
15414     /**
15415      * Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide
15416      * effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15417      * Fx class overview for valid anchor point options. Usage:
15418      *
15419      *     // default: slide the element in from the top
15420      *     el.slideIn();
15421      *
15422      *     // custom: slide the element in from the right with a 2-second duration
15423      *     el.slideIn('r', { duration: 2000 });
15424      *
15425      *     // common config options shown with default values
15426      *     el.slideIn('t', {
15427      *         easing: 'easeOut',
15428      *         duration: 500
15429      *     });
15430      *
15431      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15432      * @param {Object} [options] Object literal with any of the Fx config options
15433      * @return {Ext.Element} The Element
15434      */
15435     slideIn: function(anchor, obj, slideOut) {
15436         var me = this,
15437             elStyle = me.dom.style,
15438             beforeAnim, wrapAnim;
15439
15440         anchor = anchor || "t";
15441         obj = obj || {};
15442
15443         beforeAnim = function() {
15444             var animScope = this,
15445                 listeners = obj.listeners,
15446                 box, position, restoreSize, wrap, anim;
15447
15448             if (!slideOut) {
15449                 me.fixDisplay();
15450             }
15451
15452             box = me.getBox();
15453             if ((anchor == 't' || anchor == 'b') && box.height === 0) {
15454                 box.height = me.dom.scrollHeight;
15455             }
15456             else if ((anchor == 'l' || anchor == 'r') && box.width === 0) {
15457                 box.width = me.dom.scrollWidth;
15458             }
15459
15460             position = me.getPositioning();
15461             me.setSize(box.width, box.height);
15462
15463             wrap = me.wrap({
15464                 style: {
15465                     visibility: slideOut ? 'visible' : 'hidden'
15466                 }
15467             });
15468             wrap.setPositioning(position);
15469             if (wrap.isStyle('position', 'static')) {
15470                 wrap.position('relative');
15471             }
15472             me.clearPositioning('auto');
15473             wrap.clip();
15474
15475             // This element is temporarily positioned absolute within its wrapper.
15476             // Restore to its default, CSS-inherited visibility setting.
15477             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
15478             me.setStyle({
15479                 visibility: '',
15480                 position: 'absolute'
15481             });
15482             if (slideOut) {
15483                 wrap.setSize(box.width, box.height);
15484             }
15485
15486             switch (anchor) {
15487                 case 't':
15488                     anim = {
15489                         from: {
15490                             width: box.width + 'px',
15491                             height: '0px'
15492                         },
15493                         to: {
15494                             width: box.width + 'px',
15495                             height: box.height + 'px'
15496                         }
15497                     };
15498                     elStyle.bottom = '0px';
15499                     break;
15500                 case 'l':
15501                     anim = {
15502                         from: {
15503                             width: '0px',
15504                             height: box.height + 'px'
15505                         },
15506                         to: {
15507                             width: box.width + 'px',
15508                             height: box.height + 'px'
15509                         }
15510                     };
15511                     elStyle.right = '0px';
15512                     break;
15513                 case 'r':
15514                     anim = {
15515                         from: {
15516                             x: box.x + box.width,
15517                             width: '0px',
15518                             height: box.height + 'px'
15519                         },
15520                         to: {
15521                             x: box.x,
15522                             width: box.width + 'px',
15523                             height: box.height + 'px'
15524                         }
15525                     };
15526                     break;
15527                 case 'b':
15528                     anim = {
15529                         from: {
15530                             y: box.y + box.height,
15531                             width: box.width + 'px',
15532                             height: '0px'
15533                         },
15534                         to: {
15535                             y: box.y,
15536                             width: box.width + 'px',
15537                             height: box.height + 'px'
15538                         }
15539                     };
15540                     break;
15541                 case 'tl':
15542                     anim = {
15543                         from: {
15544                             x: box.x,
15545                             y: box.y,
15546                             width: '0px',
15547                             height: '0px'
15548                         },
15549                         to: {
15550                             width: box.width + 'px',
15551                             height: box.height + 'px'
15552                         }
15553                     };
15554                     elStyle.bottom = '0px';
15555                     elStyle.right = '0px';
15556                     break;
15557                 case 'bl':
15558                     anim = {
15559                         from: {
15560                             x: box.x + box.width,
15561                             width: '0px',
15562                             height: '0px'
15563                         },
15564                         to: {
15565                             x: box.x,
15566                             width: box.width + 'px',
15567                             height: box.height + 'px'
15568                         }
15569                     };
15570                     elStyle.right = '0px';
15571                     break;
15572                 case 'br':
15573                     anim = {
15574                         from: {
15575                             x: box.x + box.width,
15576                             y: box.y + box.height,
15577                             width: '0px',
15578                             height: '0px'
15579                         },
15580                         to: {
15581                             x: box.x,
15582                             y: box.y,
15583                             width: box.width + 'px',
15584                             height: box.height + 'px'
15585                         }
15586                     };
15587                     break;
15588                 case 'tr':
15589                     anim = {
15590                         from: {
15591                             y: box.y + box.height,
15592                             width: '0px',
15593                             height: '0px'
15594                         },
15595                         to: {
15596                             y: box.y,
15597                             width: box.width + 'px',
15598                             height: box.height + 'px'
15599                         }
15600                     };
15601                     elStyle.bottom = '0px';
15602                     break;
15603             }
15604
15605             wrap.show();
15606             wrapAnim = Ext.apply({}, obj);
15607             delete wrapAnim.listeners;
15608             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
15609                 target: wrap,
15610                 duration: 500,
15611                 easing: 'ease-out',
15612                 from: slideOut ? anim.to : anim.from,
15613                 to: slideOut ? anim.from : anim.to
15614             }));
15615
15616             // In the absence of a callback, this listener MUST be added first
15617             wrapAnim.on('afteranimate', function() {
15618                 if (slideOut) {
15619                     me.setPositioning(position);
15620                     if (obj.useDisplay) {
15621                         me.setDisplayed(false);
15622                     } else {
15623                         me.hide();
15624                     }
15625                 }
15626                 else {
15627                     me.clearPositioning();
15628                     me.setPositioning(position);
15629                 }
15630                 if (wrap.dom) {
15631                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
15632                     wrap.remove();
15633                 }
15634                 me.setSize(box.width, box.height);
15635                 animScope.end();
15636             });
15637             // Add configured listeners after
15638             if (listeners) {
15639                 wrapAnim.on(listeners);
15640             }
15641         };
15642
15643         me.animate({
15644             duration: obj.duration ? obj.duration * 2 : 1000,
15645             listeners: {
15646                 beforeanimate: {
15647                     fn: beforeAnim
15648                 },
15649                 afteranimate: {
15650                     fn: function() {
15651                         if (wrapAnim && wrapAnim.running) {
15652                             wrapAnim.end();
15653                         }
15654                     }
15655                 }
15656             }
15657         });
15658         return me;
15659     },
15660
15661
15662     /**
15663      * Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide
15664      * effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will
15665      * still take up space in the document. The element must be removed from the DOM using the 'remove' config option if
15666      * desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the
15667      * Fx class overview for valid anchor point options. Usage:
15668      *
15669      *     // default: slide the element out to the top
15670      *     el.slideOut();
15671      *
15672      *     // custom: slide the element out to the right with a 2-second duration
15673      *     el.slideOut('r', { duration: 2000 });
15674      *
15675      *     // common config options shown with default values
15676      *     el.slideOut('t', {
15677      *         easing: 'easeOut',
15678      *         duration: 500,
15679      *         remove: false,
15680      *         useDisplay: false
15681      *     });
15682      *
15683      * @param {String} [anchor='t'] One of the valid Fx anchor positions
15684      * @param {Object} [options] Object literal with any of the Fx config options
15685      * @return {Ext.Element} The Element
15686      */
15687     slideOut: function(anchor, o) {
15688         return this.slideIn(anchor, o, true);
15689     },
15690
15691     /**
15692      * Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will
15693      * be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage:
15694      *
15695      *     // default
15696      *     el.puff();
15697      *
15698      *     // common config options shown with default values
15699      *     el.puff({
15700      *         easing: 'easeOut',
15701      *         duration: 500,
15702      *         useDisplay: false
15703      *     });
15704      *
15705      * @param {Object} options (optional) Object literal with any of the Fx config options
15706      * @return {Ext.Element} The Element
15707      */
15708     puff: function(obj) {
15709         var me = this,
15710             beforeAnim;
15711         obj = Ext.applyIf(obj || {}, {
15712             easing: 'ease-out',
15713             duration: 500,
15714             useDisplay: false
15715         });
15716
15717         beforeAnim = function() {
15718             me.clearOpacity();
15719             me.show();
15720
15721             var box = me.getBox(),
15722                 fontSize = me.getStyle('fontSize'),
15723                 position = me.getPositioning();
15724             this.to = {
15725                 width: box.width * 2,
15726                 height: box.height * 2,
15727                 x: box.x - (box.width / 2),
15728                 y: box.y - (box.height /2),
15729                 opacity: 0,
15730                 fontSize: '200%'
15731             };
15732             this.on('afteranimate',function() {
15733                 if (me.dom) {
15734                     if (obj.useDisplay) {
15735                         me.setDisplayed(false);
15736                     } else {
15737                         me.hide();
15738                     }
15739                     me.clearOpacity();
15740                     me.setPositioning(position);
15741                     me.setStyle({fontSize: fontSize});
15742                 }
15743             });
15744         };
15745
15746         me.animate({
15747             duration: obj.duration,
15748             easing: obj.easing,
15749             listeners: {
15750                 beforeanimate: {
15751                     fn: beforeAnim
15752                 }
15753             }
15754         });
15755         return me;
15756     },
15757
15758     /**
15759      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
15760      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
15761      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if
15762      * desired. Usage:
15763      *
15764      *     // default
15765      *     el.switchOff();
15766      *
15767      *     // all config options shown with default values
15768      *     el.switchOff({
15769      *         easing: 'easeIn',
15770      *         duration: .3,
15771      *         remove: false,
15772      *         useDisplay: false
15773      *     });
15774      *
15775      * @param {Object} options (optional) Object literal with any of the Fx config options
15776      * @return {Ext.Element} The Element
15777      */
15778     switchOff: function(obj) {
15779         var me = this,
15780             beforeAnim;
15781
15782         obj = Ext.applyIf(obj || {}, {
15783             easing: 'ease-in',
15784             duration: 500,
15785             remove: false,
15786             useDisplay: false
15787         });
15788
15789         beforeAnim = function() {
15790             var animScope = this,
15791                 size = me.getSize(),
15792                 xy = me.getXY(),
15793                 keyframe, position;
15794             me.clearOpacity();
15795             me.clip();
15796             position = me.getPositioning();
15797
15798             keyframe = Ext.create('Ext.fx.Animator', {
15799                 target: me,
15800                 duration: obj.duration,
15801                 easing: obj.easing,
15802                 keyframes: {
15803                     33: {
15804                         opacity: 0.3
15805                     },
15806                     66: {
15807                         height: 1,
15808                         y: xy[1] + size.height / 2
15809                     },
15810                     100: {
15811                         width: 1,
15812                         x: xy[0] + size.width / 2
15813                     }
15814                 }
15815             });
15816             keyframe.on('afteranimate', function() {
15817                 if (obj.useDisplay) {
15818                     me.setDisplayed(false);
15819                 } else {
15820                     me.hide();
15821                 }
15822                 me.clearOpacity();
15823                 me.setPositioning(position);
15824                 me.setSize(size);
15825                 animScope.end();
15826             });
15827         };
15828         me.animate({
15829             duration: (obj.duration * 2),
15830             listeners: {
15831                 beforeanimate: {
15832                     fn: beforeAnim
15833                 }
15834             }
15835         });
15836         return me;
15837     },
15838
15839     /**
15840      * Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage:
15841      *
15842      *     // default: a single light blue ripple
15843      *     el.frame();
15844      *
15845      *     // custom: 3 red ripples lasting 3 seconds total
15846      *     el.frame("#ff0000", 3, { duration: 3 });
15847      *
15848      *     // common config options shown with default values
15849      *     el.frame("#C3DAF9", 1, {
15850      *         duration: 1 //duration of each individual ripple.
15851      *         // Note: Easing is not configurable and will be ignored if included
15852      *     });
15853      *
15854      * @param {String} [color='C3DAF9'] The color of the border. Should be a 6 char hex color without the leading #
15855      * (defaults to light blue).
15856      * @param {Number} [count=1] The number of ripples to display
15857      * @param {Object} [options] Object literal with any of the Fx config options
15858      * @return {Ext.Element} The Element
15859      */
15860     frame : function(color, count, obj){
15861         var me = this,
15862             beforeAnim;
15863
15864         color = color || '#C3DAF9';
15865         count = count || 1;
15866         obj = obj || {};
15867
15868         beforeAnim = function() {
15869             me.show();
15870             var animScope = this,
15871                 box = me.getBox(),
15872                 proxy = Ext.getBody().createChild({
15873                     style: {
15874                         position : 'absolute',
15875                         'pointer-events': 'none',
15876                         'z-index': 35000,
15877                         border : '0px solid ' + color
15878                     }
15879                 }),
15880                 proxyAnim;
15881             proxyAnim = Ext.create('Ext.fx.Anim', {
15882                 target: proxy,
15883                 duration: obj.duration || 1000,
15884                 iterations: count,
15885                 from: {
15886                     top: box.y,
15887                     left: box.x,
15888                     borderWidth: 0,
15889                     opacity: 1,
15890                     height: box.height,
15891                     width: box.width
15892                 },
15893                 to: {
15894                     top: box.y - 20,
15895                     left: box.x - 20,
15896                     borderWidth: 10,
15897                     opacity: 0,
15898                     height: box.height + 40,
15899                     width: box.width + 40
15900                 }
15901             });
15902             proxyAnim.on('afteranimate', function() {
15903                 proxy.remove();
15904                 animScope.end();
15905             });
15906         };
15907
15908         me.animate({
15909             duration: (obj.duration * 2) || 2000,
15910             listeners: {
15911                 beforeanimate: {
15912                     fn: beforeAnim
15913                 }
15914             }
15915         });
15916         return me;
15917     },
15918
15919     /**
15920      * Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point
15921      * of the effect. Usage:
15922      *
15923      *     // default: slide the element downward while fading out
15924      *     el.ghost();
15925      *
15926      *     // custom: slide the element out to the right with a 2-second duration
15927      *     el.ghost('r', { duration: 2000 });
15928      *
15929      *     // common config options shown with default values
15930      *     el.ghost('b', {
15931      *         easing: 'easeOut',
15932      *         duration: 500
15933      *     });
15934      *
15935      * @param {String} [anchor='b'] One of the valid Fx anchor positions
15936      * @param {Object} [options] Object literal with any of the Fx config options
15937      * @return {Ext.Element} The Element
15938      */
15939     ghost: function(anchor, obj) {
15940         var me = this,
15941             beforeAnim;
15942
15943         anchor = anchor || "b";
15944         beforeAnim = function() {
15945             var width = me.getWidth(),
15946                 height = me.getHeight(),
15947                 xy = me.getXY(),
15948                 position = me.getPositioning(),
15949                 to = {
15950                     opacity: 0
15951                 };
15952             switch (anchor) {
15953                 case 't':
15954                     to.y = xy[1] - height;
15955                     break;
15956                 case 'l':
15957                     to.x = xy[0] - width;
15958                     break;
15959                 case 'r':
15960                     to.x = xy[0] + width;
15961                     break;
15962                 case 'b':
15963                     to.y = xy[1] + height;
15964                     break;
15965                 case 'tl':
15966                     to.x = xy[0] - width;
15967                     to.y = xy[1] - height;
15968                     break;
15969                 case 'bl':
15970                     to.x = xy[0] - width;
15971                     to.y = xy[1] + height;
15972                     break;
15973                 case 'br':
15974                     to.x = xy[0] + width;
15975                     to.y = xy[1] + height;
15976                     break;
15977                 case 'tr':
15978                     to.x = xy[0] + width;
15979                     to.y = xy[1] - height;
15980                     break;
15981             }
15982             this.to = to;
15983             this.on('afteranimate', function () {
15984                 if (me.dom) {
15985                     me.hide();
15986                     me.clearOpacity();
15987                     me.setPositioning(position);
15988                 }
15989             });
15990         };
15991
15992         me.animate(Ext.applyIf(obj || {}, {
15993             duration: 500,
15994             easing: 'ease-out',
15995             listeners: {
15996                 beforeanimate: {
15997                     fn: beforeAnim
15998                 }
15999             }
16000         }));
16001         return me;
16002     },
16003
16004     /**
16005      * Highlights the Element by setting a color (applies to the background-color by default, but can be changed using
16006      * the "attr" config option) and then fading back to the original color. If no original color is available, you
16007      * should provide the "endColor" config option which will be cleared after the animation. Usage:
16008      *
16009      *     // default: highlight background to yellow
16010      *     el.highlight();
16011      *
16012      *     // custom: highlight foreground text to blue for 2 seconds
16013      *     el.highlight("0000ff", { attr: 'color', duration: 2000 });
16014      *
16015      *     // common config options shown with default values
16016      *     el.highlight("ffff9c", {
16017      *         attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
16018      *         endColor: (current color) or "ffffff",
16019      *         easing: 'easeIn',
16020      *         duration: 1000
16021      *     });
16022      *
16023      * @param {String} [color='ffff9c'] The highlight color. Should be a 6 char hex color without the leading #
16024      * @param {Object} [options] Object literal with any of the Fx config options
16025      * @return {Ext.Element} The Element
16026      */
16027     highlight: function(color, o) {
16028         var me = this,
16029             dom = me.dom,
16030             from = {},
16031             restore, to, attr, lns, event, fn;
16032
16033         o = o || {};
16034         lns = o.listeners || {};
16035         attr = o.attr || 'backgroundColor';
16036         from[attr] = color || 'ffff9c';
16037
16038         if (!o.to) {
16039             to = {};
16040             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
16041         }
16042         else {
16043             to = o.to;
16044         }
16045
16046         // Don't apply directly on lns, since we reference it in our own callbacks below
16047         o.listeners = Ext.apply(Ext.apply({}, lns), {
16048             beforeanimate: function() {
16049                 restore = dom.style[attr];
16050                 me.clearOpacity();
16051                 me.show();
16052
16053                 event = lns.beforeanimate;
16054                 if (event) {
16055                     fn = event.fn || event;
16056                     return fn.apply(event.scope || lns.scope || window, arguments);
16057                 }
16058             },
16059             afteranimate: function() {
16060                 if (dom) {
16061                     dom.style[attr] = restore;
16062                 }
16063
16064                 event = lns.afteranimate;
16065                 if (event) {
16066                     fn = event.fn || event;
16067                     fn.apply(event.scope || lns.scope || window, arguments);
16068                 }
16069             }
16070         });
16071
16072         me.animate(Ext.apply({}, o, {
16073             duration: 1000,
16074             easing: 'ease-in',
16075             from: from,
16076             to: to
16077         }));
16078         return me;
16079     },
16080
16081    /**
16082     * @deprecated 4.0
16083     * Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will
16084     * have no effect. Usage:
16085     *
16086     *     el.pause(1);
16087     *
16088     * @param {Number} seconds The length of time to pause (in seconds)
16089     * @return {Ext.Element} The Element
16090     */
16091     pause: function(ms) {
16092         var me = this;
16093         Ext.fx.Manager.setFxDefaults(me.id, {
16094             delay: ms
16095         });
16096         return me;
16097     },
16098
16099     /**
16100      * Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity`
16101      * config option. Usage:
16102      *
16103      *     // default: fade in from opacity 0 to 100%
16104      *     el.fadeIn();
16105      *
16106      *     // custom: fade in from opacity 0 to 75% over 2 seconds
16107      *     el.fadeIn({ opacity: .75, duration: 2000});
16108      *
16109      *     // common config options shown with default values
16110      *     el.fadeIn({
16111      *         opacity: 1, //can be any value between 0 and 1 (e.g. .5)
16112      *         easing: 'easeOut',
16113      *         duration: 500
16114      *     });
16115      *
16116      * @param {Object} options (optional) Object literal with any of the Fx config options
16117      * @return {Ext.Element} The Element
16118      */
16119     fadeIn: function(o) {
16120         this.animate(Ext.apply({}, o, {
16121             opacity: 1
16122         }));
16123         return this;
16124     },
16125
16126     /**
16127      * Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity`
16128      * config option. Note that IE may require `useDisplay:true` in order to redisplay correctly.
16129      * Usage:
16130      *
16131      *     // default: fade out from the element's current opacity to 0
16132      *     el.fadeOut();
16133      *
16134      *     // custom: fade out from the element's current opacity to 25% over 2 seconds
16135      *     el.fadeOut({ opacity: .25, duration: 2000});
16136      *
16137      *     // common config options shown with default values
16138      *     el.fadeOut({
16139      *         opacity: 0, //can be any value between 0 and 1 (e.g. .5)
16140      *         easing: 'easeOut',
16141      *         duration: 500,
16142      *         remove: false,
16143      *         useDisplay: false
16144      *     });
16145      *
16146      * @param {Object} options (optional) Object literal with any of the Fx config options
16147      * @return {Ext.Element} The Element
16148      */
16149     fadeOut: function(o) {
16150         this.animate(Ext.apply({}, o, {
16151             opacity: 0
16152         }));
16153         return this;
16154     },
16155
16156     /**
16157      * @deprecated 4.0
16158      * Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This
16159      * method is a convenience implementation of {@link #shift}. Usage:
16160      *
16161      *     // change height and width to 100x100 pixels
16162      *     el.scale(100, 100);
16163      *
16164      *     // common config options shown with default values.  The height and width will default to
16165      *     // the element's existing values if passed as null.
16166      *     el.scale(
16167      *         [element's width],
16168      *         [element's height], {
16169      *             easing: 'easeOut',
16170      *             duration: .35
16171      *         }
16172      *     );
16173      *
16174      * @param {Number} width The new width (pass undefined to keep the original width)
16175      * @param {Number} height The new height (pass undefined to keep the original height)
16176      * @param {Object} options (optional) Object literal with any of the Fx config options
16177      * @return {Ext.Element} The Element
16178      */
16179     scale: function(w, h, o) {
16180         this.animate(Ext.apply({}, o, {
16181             width: w,
16182             height: h
16183         }));
16184         return this;
16185     },
16186
16187     /**
16188      * @deprecated 4.0
16189      * Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these
16190      * properties not specified in the config object will not be changed. This effect requires that at least one new
16191      * dimension, position or opacity setting must be passed in on the config object in order for the function to have
16192      * any effect. Usage:
16193      *
16194      *     // slide the element horizontally to x position 200 while changing the height and opacity
16195      *     el.shift({ x: 200, height: 50, opacity: .8 });
16196      *
16197      *     // common config options shown with default values.
16198      *     el.shift({
16199      *         width: [element's width],
16200      *         height: [element's height],
16201      *         x: [element's x position],
16202      *         y: [element's y position],
16203      *         opacity: [element's opacity],
16204      *         easing: 'easeOut',
16205      *         duration: .35
16206      *     });
16207      *
16208      * @param {Object} options Object literal with any of the Fx config options
16209      * @return {Ext.Element} The Element
16210      */
16211     shift: function(config) {
16212         this.animate(config);
16213         return this;
16214     }
16215 });
16216
16217 /**
16218  * @class Ext.Element
16219  */
16220 Ext.applyIf(Ext.Element, {
16221     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
16222     camelRe: /(-[a-z])/gi,
16223     opacityRe: /alpha\(opacity=(.*)\)/i,
16224     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
16225     propertyCache: {},
16226     defaultUnit : "px",
16227     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
16228     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
16229     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
16230
16231     // Reference the prototype's version of the method. Signatures are identical.
16232     addUnits : Ext.Element.prototype.addUnits,
16233
16234     /**
16235      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16236      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16237      * @static
16238      * @param {Number/String} box The encoded margins
16239      * @return {Object} An object with margin sizes for top, right, bottom and left
16240      */
16241     parseBox : function(box) {
16242         if (Ext.isObject(box)) {
16243             return {
16244                 top: box.top || 0,
16245                 right: box.right || 0,
16246                 bottom: box.bottom || 0,
16247                 left: box.left || 0
16248             };
16249         } else {
16250             if (typeof box != 'string') {
16251                 box = box.toString();
16252             }
16253             var parts  = box.split(' '),
16254                 ln = parts.length;
16255     
16256             if (ln == 1) {
16257                 parts[1] = parts[2] = parts[3] = parts[0];
16258             }
16259             else if (ln == 2) {
16260                 parts[2] = parts[0];
16261                 parts[3] = parts[1];
16262             }
16263             else if (ln == 3) {
16264                 parts[3] = parts[1];
16265             }
16266     
16267             return {
16268                 top   :parseFloat(parts[0]) || 0,
16269                 right :parseFloat(parts[1]) || 0,
16270                 bottom:parseFloat(parts[2]) || 0,
16271                 left  :parseFloat(parts[3]) || 0
16272             };
16273         }
16274         
16275     },
16276     
16277     /**
16278      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
16279      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
16280      * @static
16281      * @param {Number/String} box The encoded margins
16282      * @param {String} units The type of units to add
16283      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
16284      */
16285     unitizeBox : function(box, units) {
16286         var A = this.addUnits,
16287             B = this.parseBox(box);
16288             
16289         return A(B.top, units) + ' ' +
16290                A(B.right, units) + ' ' +
16291                A(B.bottom, units) + ' ' +
16292                A(B.left, units);
16293         
16294     },
16295
16296     // private
16297     camelReplaceFn : function(m, a) {
16298         return a.charAt(1).toUpperCase();
16299     },
16300
16301     /**
16302      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
16303      * For example:
16304      * <ul>
16305      *  <li>border-width -> borderWidth</li>
16306      *  <li>padding-top -> paddingTop</li>
16307      * </ul>
16308      * @static
16309      * @param {String} prop The property to normalize
16310      * @return {String} The normalized string
16311      */
16312     normalize : function(prop) {
16313         if (prop == 'float') {
16314             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
16315         }
16316         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
16317     },
16318
16319     /**
16320      * Retrieves the document height
16321      * @static
16322      * @return {Number} documentHeight
16323      */
16324     getDocumentHeight: function() {
16325         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
16326     },
16327
16328     /**
16329      * Retrieves the document width
16330      * @static
16331      * @return {Number} documentWidth
16332      */
16333     getDocumentWidth: function() {
16334         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
16335     },
16336
16337     /**
16338      * Retrieves the viewport height of the window.
16339      * @static
16340      * @return {Number} viewportHeight
16341      */
16342     getViewportHeight: function(){
16343         return window.innerHeight;
16344     },
16345
16346     /**
16347      * Retrieves the viewport width of the window.
16348      * @static
16349      * @return {Number} viewportWidth
16350      */
16351     getViewportWidth : function() {
16352         return window.innerWidth;
16353     },
16354
16355     /**
16356      * Retrieves the viewport size of the window.
16357      * @static
16358      * @return {Object} object containing width and height properties
16359      */
16360     getViewSize : function() {
16361         return {
16362             width: window.innerWidth,
16363             height: window.innerHeight
16364         };
16365     },
16366
16367     /**
16368      * Retrieves the current orientation of the window. This is calculated by
16369      * determing if the height is greater than the width.
16370      * @static
16371      * @return {String} Orientation of window: 'portrait' or 'landscape'
16372      */
16373     getOrientation : function() {
16374         if (Ext.supports.OrientationChange) {
16375             return (window.orientation == 0) ? 'portrait' : 'landscape';
16376         }
16377         
16378         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
16379     },
16380
16381     /** 
16382      * Returns the top Element that is located at the passed coordinates
16383      * @static
16384      * @param {Number} x The x coordinate
16385      * @param {Number} y The y coordinate
16386      * @return {String} The found Element
16387      */
16388     fromPoint: function(x, y) {
16389         return Ext.get(document.elementFromPoint(x, y));
16390     },
16391     
16392     /**
16393      * Converts a CSS string into an object with a property for each style.
16394      * <p>
16395      * The sample code below would return an object with 2 properties, one
16396      * for background-color and one for color.</p>
16397      * <pre><code>
16398 var css = 'background-color: red;color: blue; ';
16399 console.log(Ext.Element.parseStyles(css));
16400      * </code></pre>
16401      * @static
16402      * @param {String} styles A CSS string
16403      * @return {Object} styles
16404      */
16405     parseStyles: function(styles){
16406         var out = {},
16407             cssRe = this.cssRe,
16408             matches;
16409             
16410         if (styles) {
16411             // Since we're using the g flag on the regex, we need to set the lastIndex.
16412             // This automatically happens on some implementations, but not others, see:
16413             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
16414             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
16415             cssRe.lastIndex = 0;
16416             while ((matches = cssRe.exec(styles))) {
16417                 out[matches[1]] = matches[2];
16418             }
16419         }
16420         return out;
16421     }
16422 });
16423
16424 /**
16425  * @class Ext.CompositeElementLite
16426  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
16427  * members, or to perform collective actions upon the whole set.</p>
16428  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
16429  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
16430  * Example:<pre><code>
16431 var els = Ext.select("#some-el div.some-class");
16432 // or select directly from an existing element
16433 var el = Ext.get('some-el');
16434 el.select('div.some-class');
16435
16436 els.setWidth(100); // all elements become 100 width
16437 els.hide(true); // all elements fade out and hide
16438 // or
16439 els.setWidth(100).hide(true);
16440 </code></pre>
16441  */
16442 Ext.CompositeElementLite = function(els, root){
16443     /**
16444      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
16445      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
16446      * to augment the capabilities of the CompositeElementLite class may use it when adding
16447      * methods to the class.</p>
16448      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
16449      * following siblings of selected elements, the code would be</p><code><pre>
16450 Ext.override(Ext.CompositeElementLite, {
16451     nextAll: function() {
16452         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
16453
16454 //      Loop through all elements in this Composite, accumulating
16455 //      an Array of all siblings.
16456         for (i = 0; i < l; i++) {
16457             for (n = els[i].nextSibling; n; n = n.nextSibling) {
16458                 r[++ri] = n;
16459             }
16460         }
16461
16462 //      Add all found siblings to this Composite
16463         return this.add(r);
16464     }
16465 });</pre></code>
16466      * @property {HTMLElement} elements
16467      */
16468     this.elements = [];
16469     this.add(els, root);
16470     this.el = new Ext.Element.Flyweight();
16471 };
16472
16473 Ext.CompositeElementLite.prototype = {
16474     isComposite: true,
16475
16476     // private
16477     getElement : function(el){
16478         // Set the shared flyweight dom property to the current element
16479         var e = this.el;
16480         e.dom = el;
16481         e.id = el.id;
16482         return e;
16483     },
16484
16485     // private
16486     transformElement : function(el){
16487         return Ext.getDom(el);
16488     },
16489
16490     /**
16491      * Returns the number of elements in this Composite.
16492      * @return Number
16493      */
16494     getCount : function(){
16495         return this.elements.length;
16496     },
16497     /**
16498      * Adds elements to this Composite object.
16499      * @param {HTMLElement[]/Ext.CompositeElement} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
16500      * @return {Ext.CompositeElement} This Composite object.
16501      */
16502     add : function(els, root){
16503         var me = this,
16504             elements = me.elements;
16505         if(!els){
16506             return this;
16507         }
16508         if(typeof els == "string"){
16509             els = Ext.Element.selectorFunction(els, root);
16510         }else if(els.isComposite){
16511             els = els.elements;
16512         }else if(!Ext.isIterable(els)){
16513             els = [els];
16514         }
16515
16516         for(var i = 0, len = els.length; i < len; ++i){
16517             elements.push(me.transformElement(els[i]));
16518         }
16519         return me;
16520     },
16521
16522     invoke : function(fn, args){
16523         var me = this,
16524             els = me.elements,
16525             len = els.length,
16526             e,
16527             i;
16528
16529         for(i = 0; i < len; i++) {
16530             e = els[i];
16531             if(e){
16532                 Ext.Element.prototype[fn].apply(me.getElement(e), args);
16533             }
16534         }
16535         return me;
16536     },
16537     /**
16538      * Returns a flyweight Element of the dom element object at the specified index
16539      * @param {Number} index
16540      * @return {Ext.Element}
16541      */
16542     item : function(index){
16543         var me = this,
16544             el = me.elements[index],
16545             out = null;
16546
16547         if(el){
16548             out = me.getElement(el);
16549         }
16550         return out;
16551     },
16552
16553     // fixes scope with flyweight
16554     addListener : function(eventName, handler, scope, opt){
16555         var els = this.elements,
16556             len = els.length,
16557             i, e;
16558
16559         for(i = 0; i<len; i++) {
16560             e = els[i];
16561             if(e) {
16562                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
16563             }
16564         }
16565         return this;
16566     },
16567     /**
16568      * <p>Calls the passed function for each element in this composite.</p>
16569      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
16570      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
16571      * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
16572      * a reference to the dom node, use el.dom.</b></div></li>
16573      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
16574      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
16575      * </ul>
16576      * @param {Object} [scope] The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
16577      * @return {Ext.CompositeElement} this
16578      */
16579     each : function(fn, scope){
16580         var me = this,
16581             els = me.elements,
16582             len = els.length,
16583             i, e;
16584
16585         for(i = 0; i<len; i++) {
16586             e = els[i];
16587             if(e){
16588                 e = this.getElement(e);
16589                 if(fn.call(scope || e, e, me, i) === false){
16590                     break;
16591                 }
16592             }
16593         }
16594         return me;
16595     },
16596
16597     /**
16598     * Clears this Composite and adds the elements passed.
16599     * @param {HTMLElement[]/Ext.CompositeElement} els Either an array of DOM elements, or another Composite from which to fill this Composite.
16600     * @return {Ext.CompositeElement} this
16601     */
16602     fill : function(els){
16603         var me = this;
16604         me.elements = [];
16605         me.add(els);
16606         return me;
16607     },
16608
16609     /**
16610      * Filters this composite to only elements that match the passed selector.
16611      * @param {String/Function} selector A string CSS selector or a comparison function.
16612      * The comparison function will be called with the following arguments:<ul>
16613      * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
16614      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
16615      * </ul>
16616      * @return {Ext.CompositeElement} this
16617      */
16618     filter : function(selector){
16619         var els = [],
16620             me = this,
16621             fn = Ext.isFunction(selector) ? selector
16622                 : function(el){
16623                     return el.is(selector);
16624                 };
16625
16626         me.each(function(el, self, i) {
16627             if (fn(el, i) !== false) {
16628                 els[els.length] = me.transformElement(el);
16629             }
16630         });
16631
16632         me.elements = els;
16633         return me;
16634     },
16635
16636     /**
16637      * Find the index of the passed element within the composite collection.
16638      * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
16639      * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
16640      */
16641     indexOf : function(el){
16642         return Ext.Array.indexOf(this.elements, this.transformElement(el));
16643     },
16644
16645     /**
16646     * Replaces the specified element with the passed element.
16647     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
16648     * to replace.
16649     * @param {String/Ext.Element} replacement The id of an element or the Element itself.
16650     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
16651     * @return {Ext.CompositeElement} this
16652     */
16653     replaceElement : function(el, replacement, domReplace){
16654         var index = !isNaN(el) ? el : this.indexOf(el),
16655             d;
16656         if(index > -1){
16657             replacement = Ext.getDom(replacement);
16658             if(domReplace){
16659                 d = this.elements[index];
16660                 d.parentNode.insertBefore(replacement, d);
16661                 Ext.removeNode(d);
16662             }
16663             Ext.Array.splice(this.elements, index, 1, replacement);
16664         }
16665         return this;
16666     },
16667
16668     /**
16669      * Removes all elements.
16670      */
16671     clear : function(){
16672         this.elements = [];
16673     }
16674 };
16675
16676 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
16677
16678 /**
16679  * @private
16680  * Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
16681  * This is called twice - once immediately below, and once again after additional Ext.Element
16682  * are added in Ext JS
16683  */
16684 Ext.CompositeElementLite.importElementMethods = function() {
16685     var fnName,
16686         ElProto = Ext.Element.prototype,
16687         CelProto = Ext.CompositeElementLite.prototype;
16688
16689     for (fnName in ElProto) {
16690         if (typeof ElProto[fnName] == 'function'){
16691             (function(fnName) {
16692                 CelProto[fnName] = CelProto[fnName] || function() {
16693                     return this.invoke(fnName, arguments);
16694                 };
16695             }).call(CelProto, fnName);
16696
16697         }
16698     }
16699 };
16700
16701 Ext.CompositeElementLite.importElementMethods();
16702
16703 if(Ext.DomQuery){
16704     Ext.Element.selectorFunction = Ext.DomQuery.select;
16705 }
16706
16707 /**
16708  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
16709  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16710  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16711  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
16712  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16713  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
16714  * @member Ext.Element
16715  * @method select
16716  */
16717 Ext.Element.select = function(selector, root){
16718     var els;
16719     if(typeof selector == "string"){
16720         els = Ext.Element.selectorFunction(selector, root);
16721     }else if(selector.length !== undefined){
16722         els = selector;
16723     }else{
16724     }
16725     return new Ext.CompositeElementLite(els);
16726 };
16727 /**
16728  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
16729  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16730  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16731  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
16732  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16733  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
16734  * @member Ext
16735  * @method select
16736  */
16737 Ext.select = Ext.Element.select;
16738
16739 /**
16740  * @class Ext.util.DelayedTask
16741  * 
16742  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
16743  * performing setTimeout where a new timeout cancels the old timeout. When called, the
16744  * task will wait the specified time period before executing. If durng that time period,
16745  * the task is called again, the original call will be cancelled. This continues so that
16746  * the function is only called a single time for each iteration.
16747  * 
16748  * This method is especially useful for things like detecting whether a user has finished
16749  * typing in a text field. An example would be performing validation on a keypress. You can
16750  * use this class to buffer the keypress events for a certain number of milliseconds, and
16751  * perform only if they stop for that amount of time.  
16752  * 
16753  * ## Usage
16754  * 
16755  *     var task = new Ext.util.DelayedTask(function(){
16756  *         alert(Ext.getDom('myInputField').value.length);
16757  *     });
16758  *     
16759  *     // Wait 500ms before calling our function. If the user presses another key
16760  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
16761  *     Ext.get('myInputField').on('keypress', function(){
16762  *         task.{@link #delay}(500);
16763  *     });
16764  * 
16765  * Note that we are using a DelayedTask here to illustrate a point. The configuration
16766  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
16767  * also setup a delayed task for you to buffer events.
16768  * 
16769  * @constructor The parameters to this constructor serve as defaults and are not required.
16770  * @param {Function} fn (optional) The default function to call. If not specified here, it must be specified during the {@link #delay} call.
16771  * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
16772  * function is called. If not specified, <code>this</code> will refer to the browser window.
16773  * @param {Array} args (optional) The default Array of arguments.
16774  */
16775 Ext.util.DelayedTask = function(fn, scope, args) {
16776     var me = this,
16777         id,
16778         call = function() {
16779             clearInterval(id);
16780             id = null;
16781             fn.apply(scope, args || []);
16782         };
16783
16784     /**
16785      * Cancels any pending timeout and queues a new one
16786      * @param {Number} delay The milliseconds to delay
16787      * @param {Function} newFn (optional) Overrides function passed to constructor
16788      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
16789      * is specified, <code>this</code> will refer to the browser window.
16790      * @param {Array} newArgs (optional) Overrides args passed to constructor
16791      */
16792     this.delay = function(delay, newFn, newScope, newArgs) {
16793         me.cancel();
16794         fn = newFn || fn;
16795         scope = newScope || scope;
16796         args = newArgs || args;
16797         id = setInterval(call, delay);
16798     };
16799
16800     /**
16801      * Cancel the last queued timeout
16802      */
16803     this.cancel = function(){
16804         if (id) {
16805             clearInterval(id);
16806             id = null;
16807         }
16808     };
16809 };
16810 Ext.require('Ext.util.DelayedTask', function() {
16811
16812     Ext.util.Event = Ext.extend(Object, (function() {
16813         function createBuffered(handler, listener, o, scope) {
16814             listener.task = new Ext.util.DelayedTask();
16815             return function() {
16816                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
16817             };
16818         }
16819
16820         function createDelayed(handler, listener, o, scope) {
16821             return function() {
16822                 var task = new Ext.util.DelayedTask();
16823                 if (!listener.tasks) {
16824                     listener.tasks = [];
16825                 }
16826                 listener.tasks.push(task);
16827                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
16828             };
16829         }
16830
16831         function createSingle(handler, listener, o, scope) {
16832             return function() {
16833                 listener.ev.removeListener(listener.fn, scope);
16834                 return handler.apply(scope, arguments);
16835             };
16836         }
16837
16838         return {
16839             isEvent: true,
16840
16841             constructor: function(observable, name) {
16842                 this.name = name;
16843                 this.observable = observable;
16844                 this.listeners = [];
16845             },
16846
16847             addListener: function(fn, scope, options) {
16848                 var me = this,
16849                     listener;
16850                     scope = scope || me.observable;
16851
16852
16853                 if (!me.isListening(fn, scope)) {
16854                     listener = me.createListener(fn, scope, options);
16855                     if (me.firing) {
16856                         // if we are currently firing this event, don't disturb the listener loop
16857                         me.listeners = me.listeners.slice(0);
16858                     }
16859                     me.listeners.push(listener);
16860                 }
16861             },
16862
16863             createListener: function(fn, scope, o) {
16864                 o = o || {};
16865                 scope = scope || this.observable;
16866
16867                 var listener = {
16868                         fn: fn,
16869                         scope: scope,
16870                         o: o,
16871                         ev: this
16872                     },
16873                     handler = fn;
16874
16875                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
16876                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
16877                 if (o.single) {
16878                     handler = createSingle(handler, listener, o, scope);
16879                 }
16880                 if (o.delay) {
16881                     handler = createDelayed(handler, listener, o, scope);
16882                 }
16883                 if (o.buffer) {
16884                     handler = createBuffered(handler, listener, o, scope);
16885                 }
16886
16887                 listener.fireFn = handler;
16888                 return listener;
16889             },
16890
16891             findListener: function(fn, scope) {
16892                 var listeners = this.listeners,
16893                 i = listeners.length,
16894                 listener,
16895                 s;
16896
16897                 while (i--) {
16898                     listener = listeners[i];
16899                     if (listener) {
16900                         s = listener.scope;
16901                         if (listener.fn == fn && (s == scope || s == this.observable)) {
16902                             return i;
16903                         }
16904                     }
16905                 }
16906
16907                 return - 1;
16908             },
16909
16910             isListening: function(fn, scope) {
16911                 return this.findListener(fn, scope) !== -1;
16912             },
16913
16914             removeListener: function(fn, scope) {
16915                 var me = this,
16916                     index,
16917                     listener,
16918                     k;
16919                 index = me.findListener(fn, scope);
16920                 if (index != -1) {
16921                     listener = me.listeners[index];
16922
16923                     if (me.firing) {
16924                         me.listeners = me.listeners.slice(0);
16925                     }
16926
16927                     // cancel and remove a buffered handler that hasn't fired yet
16928                     if (listener.task) {
16929                         listener.task.cancel();
16930                         delete listener.task;
16931                     }
16932
16933                     // cancel and remove all delayed handlers that haven't fired yet
16934                     k = listener.tasks && listener.tasks.length;
16935                     if (k) {
16936                         while (k--) {
16937                             listener.tasks[k].cancel();
16938                         }
16939                         delete listener.tasks;
16940                     }
16941
16942                     // remove this listener from the listeners array
16943                     Ext.Array.erase(me.listeners, index, 1);
16944                     return true;
16945                 }
16946
16947                 return false;
16948             },
16949
16950             // Iterate to stop any buffered/delayed events
16951             clearListeners: function() {
16952                 var listeners = this.listeners,
16953                     i = listeners.length;
16954
16955                 while (i--) {
16956                     this.removeListener(listeners[i].fn, listeners[i].scope);
16957                 }
16958             },
16959
16960             fire: function() {
16961                 var me = this,
16962                     listeners = me.listeners,
16963                     count = listeners.length,
16964                     i,
16965                     args,
16966                     listener;
16967
16968                 if (count > 0) {
16969                     me.firing = true;
16970                     for (i = 0; i < count; i++) {
16971                         listener = listeners[i];
16972                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
16973                         if (listener.o) {
16974                             args.push(listener.o);
16975                         }
16976                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
16977                             return (me.firing = false);
16978                         }
16979                     }
16980                 }
16981                 me.firing = false;
16982                 return true;
16983             }
16984         };
16985     })());
16986 });
16987
16988 /**
16989  * @class Ext.EventManager
16990  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
16991  * several useful events directly.
16992  * See {@link Ext.EventObject} for more details on normalized event objects.
16993  * @singleton
16994  */
16995 Ext.EventManager = {
16996
16997     // --------------------- onReady ---------------------
16998
16999     /**
17000      * Check if we have bound our global onReady listener
17001      * @private
17002      */
17003     hasBoundOnReady: false,
17004
17005     /**
17006      * Check if fireDocReady has been called
17007      * @private
17008      */
17009     hasFiredReady: false,
17010
17011     /**
17012      * Timer for the document ready event in old IE versions
17013      * @private
17014      */
17015     readyTimeout: null,
17016
17017     /**
17018      * Checks if we have bound an onreadystatechange event
17019      * @private
17020      */
17021     hasOnReadyStateChange: false,
17022
17023     /**
17024      * Holds references to any onReady functions
17025      * @private
17026      */
17027     readyEvent: new Ext.util.Event(),
17028
17029     /**
17030      * Check the ready state for old IE versions
17031      * @private
17032      * @return {Boolean} True if the document is ready
17033      */
17034     checkReadyState: function(){
17035         var me = Ext.EventManager;
17036
17037         if(window.attachEvent){
17038             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
17039             // licensed courtesy of http://developer.yahoo.com/yui/license.html
17040             if (window != top) {
17041                 return false;
17042             }
17043             try{
17044                 document.documentElement.doScroll('left');
17045             }catch(e){
17046                 return false;
17047             }
17048             me.fireDocReady();
17049             return true;
17050         }
17051         if (document.readyState == 'complete') {
17052             me.fireDocReady();
17053             return true;
17054         }
17055         me.readyTimeout = setTimeout(arguments.callee, 2);
17056         return false;
17057     },
17058
17059     /**
17060      * Binds the appropriate browser event for checking if the DOM has loaded.
17061      * @private
17062      */
17063     bindReadyEvent: function(){
17064         var me = Ext.EventManager;
17065         if (me.hasBoundOnReady) {
17066             return;
17067         }
17068
17069         if (document.addEventListener) {
17070             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
17071             // fallback, load will ~always~ fire
17072             window.addEventListener('load', me.fireDocReady, false);
17073         } else {
17074             // check if the document is ready, this will also kick off the scroll checking timer
17075             if (!me.checkReadyState()) {
17076                 document.attachEvent('onreadystatechange', me.checkReadyState);
17077                 me.hasOnReadyStateChange = true;
17078             }
17079             // fallback, onload will ~always~ fire
17080             window.attachEvent('onload', me.fireDocReady, false);
17081         }
17082         me.hasBoundOnReady = true;
17083     },
17084
17085     /**
17086      * We know the document is loaded, so trigger any onReady events.
17087      * @private
17088      */
17089     fireDocReady: function(){
17090         var me = Ext.EventManager;
17091
17092         // only unbind these events once
17093         if (!me.hasFiredReady) {
17094             me.hasFiredReady = true;
17095
17096             if (document.addEventListener) {
17097                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
17098                 window.removeEventListener('load', me.fireDocReady, false);
17099             } else {
17100                 if (me.readyTimeout !== null) {
17101                     clearTimeout(me.readyTimeout);
17102                 }
17103                 if (me.hasOnReadyStateChange) {
17104                     document.detachEvent('onreadystatechange', me.checkReadyState);
17105                 }
17106                 window.detachEvent('onload', me.fireDocReady);
17107             }
17108             Ext.supports.init();
17109         }
17110         if (!Ext.isReady) {
17111             Ext.isReady = true;
17112             me.onWindowUnload();
17113             me.readyEvent.fire();
17114         }
17115     },
17116
17117     /**
17118      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
17119      * accessed shorthanded as Ext.onReady().
17120      * @param {Function} fn The method the event invokes.
17121      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17122      * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}.
17123      */
17124     onDocumentReady: function(fn, scope, options){
17125         options = options || {};
17126         var me = Ext.EventManager,
17127             readyEvent = me.readyEvent;
17128
17129         // force single to be true so our event is only ever fired once.
17130         options.single = true;
17131
17132         // Document already loaded, let's just fire it
17133         if (Ext.isReady) {
17134             readyEvent.addListener(fn, scope, options);
17135             readyEvent.fire();
17136         } else {
17137             options.delay = options.delay || 1;
17138             readyEvent.addListener(fn, scope, options);
17139             me.bindReadyEvent();
17140         }
17141     },
17142
17143
17144     // --------------------- event binding ---------------------
17145
17146     /**
17147      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
17148      * @private
17149      */
17150     stoppedMouseDownEvent: new Ext.util.Event(),
17151
17152     /**
17153      * Options to parse for the 4th argument to addListener.
17154      * @private
17155      */
17156     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
17157
17158     /**
17159      * Get the id of the element. If one has not been assigned, automatically assign it.
17160      * @param {HTMLElement/Ext.Element} element The element to get the id for.
17161      * @return {String} id
17162      */
17163     getId : function(element) {
17164         var skipGarbageCollection = false,
17165             id;
17166
17167         element = Ext.getDom(element);
17168
17169         if (element === document || element === window) {
17170             id = element === document ? Ext.documentId : Ext.windowId;
17171         }
17172         else {
17173             id = Ext.id(element);
17174         }
17175         // skip garbage collection for special elements (window, document, iframes)
17176         if (element && (element.getElementById || element.navigator)) {
17177             skipGarbageCollection = true;
17178         }
17179
17180         if (!Ext.cache[id]){
17181             Ext.Element.addToCache(new Ext.Element(element), id);
17182             if (skipGarbageCollection) {
17183                 Ext.cache[id].skipGarbageCollection = true;
17184             }
17185         }
17186         return id;
17187     },
17188
17189     /**
17190      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
17191      * @private
17192      * @param {Object} element The element the event is for
17193      * @param {Object} event The event configuration
17194      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
17195      */
17196     prepareListenerConfig: function(element, config, isRemove){
17197         var me = this,
17198             propRe = me.propRe,
17199             key, value, args;
17200
17201         // loop over all the keys in the object
17202         for (key in config) {
17203             if (config.hasOwnProperty(key)) {
17204                 // if the key is something else then an event option
17205                 if (!propRe.test(key)) {
17206                     value = config[key];
17207                     // if the value is a function it must be something like click: function(){}, scope: this
17208                     // which means that there might be multiple event listeners with shared options
17209                     if (Ext.isFunction(value)) {
17210                         // shared options
17211                         args = [element, key, value, config.scope, config];
17212                     } else {
17213                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
17214                         args = [element, key, value.fn, value.scope, value];
17215                     }
17216
17217                     if (isRemove === true) {
17218                         me.removeListener.apply(this, args);
17219                     } else {
17220                         me.addListener.apply(me, args);
17221                     }
17222                 }
17223             }
17224         }
17225     },
17226
17227     /**
17228      * Normalize cross browser event differences
17229      * @private
17230      * @param {Object} eventName The event name
17231      * @param {Object} fn The function to execute
17232      * @return {Object} The new event name/function
17233      */
17234     normalizeEvent: function(eventName, fn){
17235         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
17236             if (fn) {
17237                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
17238             }
17239             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
17240         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
17241             eventName = 'DOMMouseScroll';
17242         }
17243         return {
17244             eventName: eventName,
17245             fn: fn
17246         };
17247     },
17248
17249     /**
17250      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
17251      * @private
17252      * @param {Object} event
17253      */
17254     contains: function(event){
17255         var parent = event.browserEvent.currentTarget,
17256             child = this.getRelatedTarget(event);
17257
17258         if (parent && parent.firstChild) {
17259             while (child) {
17260                 if (child === parent) {
17261                     return false;
17262                 }
17263                 child = child.parentNode;
17264                 if (child && (child.nodeType != 1)) {
17265                     child = null;
17266                 }
17267             }
17268         }
17269         return true;
17270     },
17271
17272     /**
17273     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
17274     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
17275     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
17276     * @param {String} eventName The name of the event to listen for.
17277     * @param {Function} handler The handler function the event invokes. This function is passed
17278     * the following parameters:<ul>
17279     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
17280     * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
17281     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
17282     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
17283     * </ul>
17284     * @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>.
17285     * @param {Object} options (optional) An object containing handler configuration properties.
17286     * This may contain any of the following properties:<ul>
17287     * <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>
17288     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
17289     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
17290     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
17291     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
17292     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
17293     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
17294     * <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>
17295     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
17296     * by the specified number of milliseconds. If the event fires again within that time, the original
17297     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
17298     * <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>
17299     * </ul><br>
17300     * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
17301     */
17302     addListener: function(element, eventName, fn, scope, options){
17303         // Check if we've been passed a "config style" event.
17304         if (typeof eventName !== 'string') {
17305             this.prepareListenerConfig(element, eventName);
17306             return;
17307         }
17308
17309         var dom = Ext.getDom(element),
17310             bind,
17311             wrap;
17312
17313
17314         // create the wrapper function
17315         options = options || {};
17316
17317         bind = this.normalizeEvent(eventName, fn);
17318         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
17319
17320
17321         if (dom.attachEvent) {
17322             dom.attachEvent('on' + bind.eventName, wrap);
17323         } else {
17324             dom.addEventListener(bind.eventName, wrap, options.capture || false);
17325         }
17326
17327         if (dom == document && eventName == 'mousedown') {
17328             this.stoppedMouseDownEvent.addListener(wrap);
17329         }
17330
17331         // add all required data into the event cache
17332         this.getEventListenerCache(dom, eventName).push({
17333             fn: fn,
17334             wrap: wrap,
17335             scope: scope
17336         });
17337     },
17338
17339     /**
17340     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
17341     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
17342     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
17343     * @param {String} eventName The name of the event.
17344     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
17345     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
17346     * then this must refer to the same object.
17347     */
17348     removeListener : function(element, eventName, fn, scope) {
17349         // handle our listener config object syntax
17350         if (typeof eventName !== 'string') {
17351             this.prepareListenerConfig(element, eventName, true);
17352             return;
17353         }
17354
17355         var dom = Ext.getDom(element),
17356             cache = this.getEventListenerCache(dom, eventName),
17357             bindName = this.normalizeEvent(eventName).eventName,
17358             i = cache.length, j,
17359             listener, wrap, tasks;
17360
17361
17362         while (i--) {
17363             listener = cache[i];
17364
17365             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
17366                 wrap = listener.wrap;
17367
17368                 // clear buffered calls
17369                 if (wrap.task) {
17370                     clearTimeout(wrap.task);
17371                     delete wrap.task;
17372                 }
17373
17374                 // clear delayed calls
17375                 j = wrap.tasks && wrap.tasks.length;
17376                 if (j) {
17377                     while (j--) {
17378                         clearTimeout(wrap.tasks[j]);
17379                     }
17380                     delete wrap.tasks;
17381                 }
17382
17383                 if (dom.detachEvent) {
17384                     dom.detachEvent('on' + bindName, wrap);
17385                 } else {
17386                     dom.removeEventListener(bindName, wrap, false);
17387                 }
17388
17389                 if (wrap && dom == document && eventName == 'mousedown') {
17390                     this.stoppedMouseDownEvent.removeListener(wrap);
17391                 }
17392
17393                 // remove listener from cache
17394                 Ext.Array.erase(cache, i, 1);
17395             }
17396         }
17397     },
17398
17399     /**
17400     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
17401     * directly on an Element in favor of calling this version.
17402     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
17403     */
17404     removeAll : function(element){
17405         var dom = Ext.getDom(element),
17406             cache, ev;
17407         if (!dom) {
17408             return;
17409         }
17410         cache = this.getElementEventCache(dom);
17411
17412         for (ev in cache) {
17413             if (cache.hasOwnProperty(ev)) {
17414                 this.removeListener(dom, ev);
17415             }
17416         }
17417         Ext.cache[dom.id].events = {};
17418     },
17419
17420     /**
17421      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
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      * @param {String} eventName (optional) The name of the event.
17425      */
17426     purgeElement : function(element, eventName) {
17427         var dom = Ext.getDom(element),
17428             i = 0, len;
17429
17430         if(eventName) {
17431             this.removeListener(dom, eventName);
17432         }
17433         else {
17434             this.removeAll(dom);
17435         }
17436
17437         if(dom && dom.childNodes) {
17438             for(len = element.childNodes.length; i < len; i++) {
17439                 this.purgeElement(element.childNodes[i], eventName);
17440             }
17441         }
17442     },
17443
17444     /**
17445      * Create the wrapper function for the event
17446      * @private
17447      * @param {HTMLElement} dom The dom element
17448      * @param {String} ename The event name
17449      * @param {Function} fn The function to execute
17450      * @param {Object} scope The scope to execute callback in
17451      * @param {Object} options The options
17452      * @return {Function} the wrapper function
17453      */
17454     createListenerWrap : function(dom, ename, fn, scope, options) {
17455         options = options || {};
17456
17457         var f, gen;
17458
17459         return function wrap(e, args) {
17460             // Compile the implementation upon first firing
17461             if (!gen) {
17462                 f = ['if(!Ext) {return;}'];
17463
17464                 if(options.buffer || options.delay || options.freezeEvent) {
17465                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
17466                 } else {
17467                     f.push('e = Ext.EventObject.setEvent(e);');
17468                 }
17469
17470                 if (options.delegate) {
17471                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
17472                     f.push('if(!t) {return;}');
17473                 } else {
17474                     f.push('var t = e.target;');
17475                 }
17476
17477                 if (options.target) {
17478                     f.push('if(e.target !== options.target) {return;}');
17479                 }
17480
17481                 if(options.stopEvent) {
17482                     f.push('e.stopEvent();');
17483                 } else {
17484                     if(options.preventDefault) {
17485                         f.push('e.preventDefault();');
17486                     }
17487                     if(options.stopPropagation) {
17488                         f.push('e.stopPropagation();');
17489                     }
17490                 }
17491
17492                 if(options.normalized === false) {
17493                     f.push('e = e.browserEvent;');
17494                 }
17495
17496                 if(options.buffer) {
17497                     f.push('(wrap.task && clearTimeout(wrap.task));');
17498                     f.push('wrap.task = setTimeout(function(){');
17499                 }
17500
17501                 if(options.delay) {
17502                     f.push('wrap.tasks = wrap.tasks || [];');
17503                     f.push('wrap.tasks.push(setTimeout(function(){');
17504                 }
17505
17506                 // finally call the actual handler fn
17507                 f.push('fn.call(scope || dom, e, t, options);');
17508
17509                 if(options.single) {
17510                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
17511                 }
17512
17513                 if(options.delay) {
17514                     f.push('}, ' + options.delay + '));');
17515                 }
17516
17517                 if(options.buffer) {
17518                     f.push('}, ' + options.buffer + ');');
17519                 }
17520
17521                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
17522             }
17523
17524             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
17525         };
17526     },
17527
17528     /**
17529      * Get the event cache for a particular element for a particular event
17530      * @private
17531      * @param {HTMLElement} element The element
17532      * @param {Object} eventName The event name
17533      * @return {Array} The events for the element
17534      */
17535     getEventListenerCache : function(element, eventName) {
17536         if (!element) {
17537             return [];
17538         }
17539
17540         var eventCache = this.getElementEventCache(element);
17541         return eventCache[eventName] || (eventCache[eventName] = []);
17542     },
17543
17544     /**
17545      * Gets the event cache for the object
17546      * @private
17547      * @param {HTMLElement} element The element
17548      * @return {Object} The event cache for the object
17549      */
17550     getElementEventCache : function(element) {
17551         if (!element) {
17552             return {};
17553         }
17554         var elementCache = Ext.cache[this.getId(element)];
17555         return elementCache.events || (elementCache.events = {});
17556     },
17557
17558     // --------------------- utility methods ---------------------
17559     mouseLeaveRe: /(mouseout|mouseleave)/,
17560     mouseEnterRe: /(mouseover|mouseenter)/,
17561
17562     /**
17563      * Stop the event (preventDefault and stopPropagation)
17564      * @param {Event} The event to stop
17565      */
17566     stopEvent: function(event) {
17567         this.stopPropagation(event);
17568         this.preventDefault(event);
17569     },
17570
17571     /**
17572      * Cancels bubbling of the event.
17573      * @param {Event} The event to stop bubbling.
17574      */
17575     stopPropagation: function(event) {
17576         event = event.browserEvent || event;
17577         if (event.stopPropagation) {
17578             event.stopPropagation();
17579         } else {
17580             event.cancelBubble = true;
17581         }
17582     },
17583
17584     /**
17585      * Prevents the browsers default handling of the event.
17586      * @param {Event} The event to prevent the default
17587      */
17588     preventDefault: function(event) {
17589         event = event.browserEvent || event;
17590         if (event.preventDefault) {
17591             event.preventDefault();
17592         } else {
17593             event.returnValue = false;
17594             // Some keys events require setting the keyCode to -1 to be prevented
17595             try {
17596               // all ctrl + X and F1 -> F12
17597               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
17598                   event.keyCode = -1;
17599               }
17600             } catch (e) {
17601                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
17602             }
17603         }
17604     },
17605
17606     /**
17607      * Gets the related target from the event.
17608      * @param {Object} event The event
17609      * @return {HTMLElement} The related target.
17610      */
17611     getRelatedTarget: function(event) {
17612         event = event.browserEvent || event;
17613         var target = event.relatedTarget;
17614         if (!target) {
17615             if (this.mouseLeaveRe.test(event.type)) {
17616                 target = event.toElement;
17617             } else if (this.mouseEnterRe.test(event.type)) {
17618                 target = event.fromElement;
17619             }
17620         }
17621         return this.resolveTextNode(target);
17622     },
17623
17624     /**
17625      * Gets the x coordinate from the event
17626      * @param {Object} event The event
17627      * @return {Number} The x coordinate
17628      */
17629     getPageX: function(event) {
17630         return this.getXY(event)[0];
17631     },
17632
17633     /**
17634      * Gets the y coordinate from the event
17635      * @param {Object} event The event
17636      * @return {Number} The y coordinate
17637      */
17638     getPageY: function(event) {
17639         return this.getXY(event)[1];
17640     },
17641
17642     /**
17643      * Gets the x & y coordinate from the event
17644      * @param {Object} event The event
17645      * @return {Number[]} The x/y coordinate
17646      */
17647     getPageXY: function(event) {
17648         event = event.browserEvent || event;
17649         var x = event.pageX,
17650             y = event.pageY,
17651             doc = document.documentElement,
17652             body = document.body;
17653
17654         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
17655         if (!x && x !== 0) {
17656             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
17657             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
17658         }
17659         return [x, y];
17660     },
17661
17662     /**
17663      * Gets the target of the event.
17664      * @param {Object} event The event
17665      * @return {HTMLElement} target
17666      */
17667     getTarget: function(event) {
17668         event = event.browserEvent || event;
17669         return this.resolveTextNode(event.target || event.srcElement);
17670     },
17671
17672     /**
17673      * Resolve any text nodes accounting for browser differences.
17674      * @private
17675      * @param {HTMLElement} node The node
17676      * @return {HTMLElement} The resolved node
17677      */
17678     // 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.
17679     resolveTextNode: Ext.isGecko ?
17680         function(node) {
17681             if (!node) {
17682                 return;
17683             }
17684             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
17685             var s = HTMLElement.prototype.toString.call(node);
17686             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
17687                 return;
17688             }
17689                 return node.nodeType == 3 ? node.parentNode: node;
17690             }: function(node) {
17691                 return node && node.nodeType == 3 ? node.parentNode: node;
17692             },
17693
17694     // --------------------- custom event binding ---------------------
17695
17696     // Keep track of the current width/height
17697     curWidth: 0,
17698     curHeight: 0,
17699
17700     /**
17701      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
17702      * passes new viewport width and height to handlers.
17703      * @param {Function} fn      The handler function the window resize event invokes.
17704      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17705      * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
17706      */
17707     onWindowResize: function(fn, scope, options){
17708         var resize = this.resizeEvent;
17709         if(!resize){
17710             this.resizeEvent = resize = new Ext.util.Event();
17711             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
17712         }
17713         resize.addListener(fn, scope, options);
17714     },
17715
17716     /**
17717      * Fire the resize event.
17718      * @private
17719      */
17720     fireResize: function(){
17721         var me = this,
17722             w = Ext.Element.getViewWidth(),
17723             h = Ext.Element.getViewHeight();
17724
17725          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
17726          if(me.curHeight != h || me.curWidth != w){
17727              me.curHeight = h;
17728              me.curWidth = w;
17729              me.resizeEvent.fire(w, h);
17730          }
17731     },
17732
17733     /**
17734      * Removes the passed window resize listener.
17735      * @param {Function} fn        The method the event invokes
17736      * @param {Object}   scope    The scope of handler
17737      */
17738     removeResizeListener: function(fn, scope){
17739         if (this.resizeEvent) {
17740             this.resizeEvent.removeListener(fn, scope);
17741         }
17742     },
17743
17744     onWindowUnload: function() {
17745         var unload = this.unloadEvent;
17746         if (!unload) {
17747             this.unloadEvent = unload = new Ext.util.Event();
17748             this.addListener(window, 'unload', this.fireUnload, this);
17749         }
17750     },
17751
17752     /**
17753      * Fires the unload event for items bound with onWindowUnload
17754      * @private
17755      */
17756     fireUnload: function() {
17757         // wrap in a try catch, could have some problems during unload
17758         try {
17759             this.removeUnloadListener();
17760             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
17761             if (Ext.isGecko3) {
17762                 var gridviews = Ext.ComponentQuery.query('gridview'),
17763                     i = 0,
17764                     ln = gridviews.length;
17765                 for (; i < ln; i++) {
17766                     gridviews[i].scrollToTop();
17767                 }
17768             }
17769             // Purge all elements in the cache
17770             var el,
17771                 cache = Ext.cache;
17772             for (el in cache) {
17773                 if (cache.hasOwnProperty(el)) {
17774                     Ext.EventManager.removeAll(el);
17775                 }
17776             }
17777         } catch(e) {
17778         }
17779     },
17780
17781     /**
17782      * Removes the passed window unload listener.
17783      * @param {Function} fn        The method the event invokes
17784      * @param {Object}   scope    The scope of handler
17785      */
17786     removeUnloadListener: function(){
17787         if (this.unloadEvent) {
17788             this.removeListener(window, 'unload', this.fireUnload);
17789         }
17790     },
17791
17792     /**
17793      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
17794      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
17795      * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
17796      * @private
17797      */
17798     useKeyDown: Ext.isWebKit ?
17799                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
17800                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
17801
17802     /**
17803      * Indicates which event to use for getting key presses.
17804      * @return {String} The appropriate event name.
17805      */
17806     getKeyEvent: function(){
17807         return this.useKeyDown ? 'keydown' : 'keypress';
17808     }
17809 };
17810
17811 /**
17812  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
17813  * @member Ext
17814  * @method onReady
17815  */
17816 Ext.onReady = function(fn, scope, options) {
17817     Ext.Loader.onReady(fn, scope, true, options);
17818 };
17819
17820 /**
17821  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
17822  * @member Ext
17823  * @method onDocumentReady
17824  */
17825 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
17826
17827 /**
17828  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
17829  * @member Ext.EventManager
17830  * @method on
17831  */
17832 Ext.EventManager.on = Ext.EventManager.addListener;
17833
17834 /**
17835  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
17836  * @member Ext.EventManager
17837  * @method un
17838  */
17839 Ext.EventManager.un = Ext.EventManager.removeListener;
17840
17841 (function(){
17842     var initExtCss = function() {
17843         // find the body element
17844         var bd = document.body || document.getElementsByTagName('body')[0],
17845             baseCSSPrefix = Ext.baseCSSPrefix,
17846             cls = [baseCSSPrefix + 'body'],
17847             htmlCls = [],
17848             html;
17849
17850         if (!bd) {
17851             return false;
17852         }
17853
17854         html = bd.parentNode;
17855
17856         function add (c) {
17857             cls.push(baseCSSPrefix + c);
17858         }
17859
17860         //Let's keep this human readable!
17861         if (Ext.isIE) {
17862             add('ie');
17863
17864             // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
17865             // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
17866             // additional classes:
17867             //
17868             //      x-ie7p      : IE7+      :  7 <= ieVer
17869             //      x-ie7m      : IE7-      :  ieVer <= 7
17870             //      x-ie8p      : IE8+      :  8 <= ieVer
17871             //      x-ie8m      : IE8-      :  ieVer <= 8
17872             //      x-ie9p      : IE9+      :  9 <= ieVer
17873             //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
17874             //
17875             if (Ext.isIE6) {
17876                 add('ie6');
17877             } else { // ignore pre-IE6 :)
17878                 add('ie7p');
17879
17880                 if (Ext.isIE7) {
17881                     add('ie7');
17882                 } else {
17883                     add('ie8p');
17884
17885                     if (Ext.isIE8) {
17886                         add('ie8');
17887                     } else {
17888                         add('ie9p');
17889
17890                         if (Ext.isIE9) {
17891                             add('ie9');
17892                         }
17893                     }
17894                 }
17895             }
17896
17897             if (Ext.isIE6 || Ext.isIE7) {
17898                 add('ie7m');
17899             }
17900             if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
17901                 add('ie8m');
17902             }
17903             if (Ext.isIE7 || Ext.isIE8) {
17904                 add('ie78');
17905             }
17906         }
17907         if (Ext.isGecko) {
17908             add('gecko');
17909             if (Ext.isGecko3) {
17910                 add('gecko3');
17911             }
17912             if (Ext.isGecko4) {
17913                 add('gecko4');
17914             }
17915             if (Ext.isGecko5) {
17916                 add('gecko5');
17917             }
17918         }
17919         if (Ext.isOpera) {
17920             add('opera');
17921         }
17922         if (Ext.isWebKit) {
17923             add('webkit');
17924         }
17925         if (Ext.isSafari) {
17926             add('safari');
17927             if (Ext.isSafari2) {
17928                 add('safari2');
17929             }
17930             if (Ext.isSafari3) {
17931                 add('safari3');
17932             }
17933             if (Ext.isSafari4) {
17934                 add('safari4');
17935             }
17936             if (Ext.isSafari5) {
17937                 add('safari5');
17938             }
17939         }
17940         if (Ext.isChrome) {
17941             add('chrome');
17942         }
17943         if (Ext.isMac) {
17944             add('mac');
17945         }
17946         if (Ext.isLinux) {
17947             add('linux');
17948         }
17949         if (!Ext.supports.CSS3BorderRadius) {
17950             add('nbr');
17951         }
17952         if (!Ext.supports.CSS3LinearGradient) {
17953             add('nlg');
17954         }
17955         if (!Ext.scopeResetCSS) {
17956             add('reset');
17957         }
17958
17959         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
17960         if (html) {
17961             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
17962                 Ext.isBorderBox = false;
17963             }
17964             else {
17965                 Ext.isBorderBox = true;
17966             }
17967
17968             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
17969             if (!Ext.isStrict) {
17970                 htmlCls.push(baseCSSPrefix + 'quirks');
17971             }
17972             Ext.fly(html, '_internal').addCls(htmlCls);
17973         }
17974
17975         Ext.fly(bd, '_internal').addCls(cls);
17976         return true;
17977     };
17978
17979     Ext.onReady(initExtCss);
17980 })();
17981
17982 /**
17983  * @class Ext.EventObject
17984
17985 Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
17986 wraps the browser's native event-object normalizing cross-browser differences,
17987 such as which mouse button is clicked, keys pressed, mechanisms to stop
17988 event-propagation along with a method to prevent default actions from taking place.
17989
17990 For example:
17991
17992     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
17993         e.preventDefault();
17994         var target = e.getTarget(); // same as t (the target HTMLElement)
17995         ...
17996     }
17997
17998     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.Element}
17999     myDiv.on(         // 'on' is shorthand for addListener
18000         "click",      // perform an action on click of myDiv
18001         handleClick   // reference to the action handler
18002     );
18003
18004     // other methods to do the same:
18005     Ext.EventManager.on("myDiv", 'click', handleClick);
18006     Ext.EventManager.addListener("myDiv", 'click', handleClick);
18007
18008  * @singleton
18009  * @markdown
18010  */
18011 Ext.define('Ext.EventObjectImpl', {
18012     uses: ['Ext.util.Point'],
18013
18014     /** Key constant @type Number */
18015     BACKSPACE: 8,
18016     /** Key constant @type Number */
18017     TAB: 9,
18018     /** Key constant @type Number */
18019     NUM_CENTER: 12,
18020     /** Key constant @type Number */
18021     ENTER: 13,
18022     /** Key constant @type Number */
18023     RETURN: 13,
18024     /** Key constant @type Number */
18025     SHIFT: 16,
18026     /** Key constant @type Number */
18027     CTRL: 17,
18028     /** Key constant @type Number */
18029     ALT: 18,
18030     /** Key constant @type Number */
18031     PAUSE: 19,
18032     /** Key constant @type Number */
18033     CAPS_LOCK: 20,
18034     /** Key constant @type Number */
18035     ESC: 27,
18036     /** Key constant @type Number */
18037     SPACE: 32,
18038     /** Key constant @type Number */
18039     PAGE_UP: 33,
18040     /** Key constant @type Number */
18041     PAGE_DOWN: 34,
18042     /** Key constant @type Number */
18043     END: 35,
18044     /** Key constant @type Number */
18045     HOME: 36,
18046     /** Key constant @type Number */
18047     LEFT: 37,
18048     /** Key constant @type Number */
18049     UP: 38,
18050     /** Key constant @type Number */
18051     RIGHT: 39,
18052     /** Key constant @type Number */
18053     DOWN: 40,
18054     /** Key constant @type Number */
18055     PRINT_SCREEN: 44,
18056     /** Key constant @type Number */
18057     INSERT: 45,
18058     /** Key constant @type Number */
18059     DELETE: 46,
18060     /** Key constant @type Number */
18061     ZERO: 48,
18062     /** Key constant @type Number */
18063     ONE: 49,
18064     /** Key constant @type Number */
18065     TWO: 50,
18066     /** Key constant @type Number */
18067     THREE: 51,
18068     /** Key constant @type Number */
18069     FOUR: 52,
18070     /** Key constant @type Number */
18071     FIVE: 53,
18072     /** Key constant @type Number */
18073     SIX: 54,
18074     /** Key constant @type Number */
18075     SEVEN: 55,
18076     /** Key constant @type Number */
18077     EIGHT: 56,
18078     /** Key constant @type Number */
18079     NINE: 57,
18080     /** Key constant @type Number */
18081     A: 65,
18082     /** Key constant @type Number */
18083     B: 66,
18084     /** Key constant @type Number */
18085     C: 67,
18086     /** Key constant @type Number */
18087     D: 68,
18088     /** Key constant @type Number */
18089     E: 69,
18090     /** Key constant @type Number */
18091     F: 70,
18092     /** Key constant @type Number */
18093     G: 71,
18094     /** Key constant @type Number */
18095     H: 72,
18096     /** Key constant @type Number */
18097     I: 73,
18098     /** Key constant @type Number */
18099     J: 74,
18100     /** Key constant @type Number */
18101     K: 75,
18102     /** Key constant @type Number */
18103     L: 76,
18104     /** Key constant @type Number */
18105     M: 77,
18106     /** Key constant @type Number */
18107     N: 78,
18108     /** Key constant @type Number */
18109     O: 79,
18110     /** Key constant @type Number */
18111     P: 80,
18112     /** Key constant @type Number */
18113     Q: 81,
18114     /** Key constant @type Number */
18115     R: 82,
18116     /** Key constant @type Number */
18117     S: 83,
18118     /** Key constant @type Number */
18119     T: 84,
18120     /** Key constant @type Number */
18121     U: 85,
18122     /** Key constant @type Number */
18123     V: 86,
18124     /** Key constant @type Number */
18125     W: 87,
18126     /** Key constant @type Number */
18127     X: 88,
18128     /** Key constant @type Number */
18129     Y: 89,
18130     /** Key constant @type Number */
18131     Z: 90,
18132     /** Key constant @type Number */
18133     CONTEXT_MENU: 93,
18134     /** Key constant @type Number */
18135     NUM_ZERO: 96,
18136     /** Key constant @type Number */
18137     NUM_ONE: 97,
18138     /** Key constant @type Number */
18139     NUM_TWO: 98,
18140     /** Key constant @type Number */
18141     NUM_THREE: 99,
18142     /** Key constant @type Number */
18143     NUM_FOUR: 100,
18144     /** Key constant @type Number */
18145     NUM_FIVE: 101,
18146     /** Key constant @type Number */
18147     NUM_SIX: 102,
18148     /** Key constant @type Number */
18149     NUM_SEVEN: 103,
18150     /** Key constant @type Number */
18151     NUM_EIGHT: 104,
18152     /** Key constant @type Number */
18153     NUM_NINE: 105,
18154     /** Key constant @type Number */
18155     NUM_MULTIPLY: 106,
18156     /** Key constant @type Number */
18157     NUM_PLUS: 107,
18158     /** Key constant @type Number */
18159     NUM_MINUS: 109,
18160     /** Key constant @type Number */
18161     NUM_PERIOD: 110,
18162     /** Key constant @type Number */
18163     NUM_DIVISION: 111,
18164     /** Key constant @type Number */
18165     F1: 112,
18166     /** Key constant @type Number */
18167     F2: 113,
18168     /** Key constant @type Number */
18169     F3: 114,
18170     /** Key constant @type Number */
18171     F4: 115,
18172     /** Key constant @type Number */
18173     F5: 116,
18174     /** Key constant @type Number */
18175     F6: 117,
18176     /** Key constant @type Number */
18177     F7: 118,
18178     /** Key constant @type Number */
18179     F8: 119,
18180     /** Key constant @type Number */
18181     F9: 120,
18182     /** Key constant @type Number */
18183     F10: 121,
18184     /** Key constant @type Number */
18185     F11: 122,
18186     /** Key constant @type Number */
18187     F12: 123,
18188     /**
18189      * The mouse wheel delta scaling factor. This value depends on browser version and OS and
18190      * attempts to produce a similar scrolling experience across all platforms and browsers.
18191      *
18192      * To change this value:
18193      *
18194      *      Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
18195      *
18196      * @type Number
18197      * @markdown
18198      */
18199     WHEEL_SCALE: (function () {
18200         var scale;
18201
18202         if (Ext.isGecko) {
18203             // Firefox uses 3 on all platforms
18204             scale = 3;
18205         } else if (Ext.isMac) {
18206             // Continuous scrolling devices have momentum and produce much more scroll than
18207             // discrete devices on the same OS and browser. To make things exciting, Safari
18208             // (and not Chrome) changed from small values to 120 (like IE).
18209
18210             if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
18211                 // Safari changed the scrolling factor to match IE (for details see
18212                 // https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
18213                 // change was introduced was 532.0
18214                 //      Detailed discussion:
18215                 //      https://bugs.webkit.org/show_bug.cgi?id=29601
18216                 //      http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
18217                 scale = 120;
18218             } else {
18219                 // MS optical wheel mouse produces multiples of 12 which is close enough
18220                 // to help tame the speed of the continuous mice...
18221                 scale = 12;
18222             }
18223
18224             // Momentum scrolling produces very fast scrolling, so increase the scale factor
18225             // to help produce similar results cross platform. This could be even larger and
18226             // it would help those mice, but other mice would become almost unusable as a
18227             // result (since we cannot tell which device type is in use).
18228             scale *= 3;
18229         } else {
18230             // IE, Opera and other Windows browsers use 120.
18231             scale = 120;
18232         }
18233
18234         return scale;
18235     })(),
18236
18237     /**
18238      * Simple click regex
18239      * @private
18240      */
18241     clickRe: /(dbl)?click/,
18242     // safari keypress events for special keys return bad keycodes
18243     safariKeys: {
18244         3: 13, // enter
18245         63234: 37, // left
18246         63235: 39, // right
18247         63232: 38, // up
18248         63233: 40, // down
18249         63276: 33, // page up
18250         63277: 34, // page down
18251         63272: 46, // delete
18252         63273: 36, // home
18253         63275: 35 // end
18254     },
18255     // normalize button clicks, don't see any way to feature detect this.
18256     btnMap: Ext.isIE ? {
18257         1: 0,
18258         4: 1,
18259         2: 2
18260     } : {
18261         0: 0,
18262         1: 1,
18263         2: 2
18264     },
18265
18266     constructor: function(event, freezeEvent){
18267         if (event) {
18268             this.setEvent(event.browserEvent || event, freezeEvent);
18269         }
18270     },
18271
18272     setEvent: function(event, freezeEvent){
18273         var me = this, button, options;
18274
18275         if (event == me || (event && event.browserEvent)) { // already wrapped
18276             return event;
18277         }
18278         me.browserEvent = event;
18279         if (event) {
18280             // normalize buttons
18281             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
18282             if (me.clickRe.test(event.type) && button == -1) {
18283                 button = 0;
18284             }
18285             options = {
18286                 type: event.type,
18287                 button: button,
18288                 shiftKey: event.shiftKey,
18289                 // mac metaKey behaves like ctrlKey
18290                 ctrlKey: event.ctrlKey || event.metaKey || false,
18291                 altKey: event.altKey,
18292                 // in getKey these will be normalized for the mac
18293                 keyCode: event.keyCode,
18294                 charCode: event.charCode,
18295                 // cache the targets for the delayed and or buffered events
18296                 target: Ext.EventManager.getTarget(event),
18297                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
18298                 currentTarget: event.currentTarget,
18299                 xy: (freezeEvent ? me.getXY() : null)
18300             };
18301         } else {
18302             options = {
18303                 button: -1,
18304                 shiftKey: false,
18305                 ctrlKey: false,
18306                 altKey: false,
18307                 keyCode: 0,
18308                 charCode: 0,
18309                 target: null,
18310                 xy: [0, 0]
18311             };
18312         }
18313         Ext.apply(me, options);
18314         return me;
18315     },
18316
18317     /**
18318      * Stop the event (preventDefault and stopPropagation)
18319      */
18320     stopEvent: function(){
18321         this.stopPropagation();
18322         this.preventDefault();
18323     },
18324
18325     /**
18326      * Prevents the browsers default handling of the event.
18327      */
18328     preventDefault: function(){
18329         if (this.browserEvent) {
18330             Ext.EventManager.preventDefault(this.browserEvent);
18331         }
18332     },
18333
18334     /**
18335      * Cancels bubbling of the event.
18336      */
18337     stopPropagation: function(){
18338         var browserEvent = this.browserEvent;
18339
18340         if (browserEvent) {
18341             if (browserEvent.type == 'mousedown') {
18342                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
18343             }
18344             Ext.EventManager.stopPropagation(browserEvent);
18345         }
18346     },
18347
18348     /**
18349      * Gets the character code for the event.
18350      * @return {Number}
18351      */
18352     getCharCode: function(){
18353         return this.charCode || this.keyCode;
18354     },
18355
18356     /**
18357      * Returns a normalized keyCode for the event.
18358      * @return {Number} The key code
18359      */
18360     getKey: function(){
18361         return this.normalizeKey(this.keyCode || this.charCode);
18362     },
18363
18364     /**
18365      * Normalize key codes across browsers
18366      * @private
18367      * @param {Number} key The key code
18368      * @return {Number} The normalized code
18369      */
18370     normalizeKey: function(key){
18371         // can't feature detect this
18372         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
18373     },
18374
18375     /**
18376      * Gets the x coordinate of the event.
18377      * @return {Number}
18378      * @deprecated 4.0 Replaced by {@link #getX}
18379      */
18380     getPageX: function(){
18381         return this.getX();
18382     },
18383
18384     /**
18385      * Gets the y coordinate of the event.
18386      * @return {Number}
18387      * @deprecated 4.0 Replaced by {@link #getY}
18388      */
18389     getPageY: function(){
18390         return this.getY();
18391     },
18392
18393     /**
18394      * Gets the x coordinate of the event.
18395      * @return {Number}
18396      */
18397     getX: function() {
18398         return this.getXY()[0];
18399     },
18400
18401     /**
18402      * Gets the y coordinate of the event.
18403      * @return {Number}
18404      */
18405     getY: function() {
18406         return this.getXY()[1];
18407     },
18408
18409     /**
18410      * Gets the page coordinates of the event.
18411      * @return {Number[]} The xy values like [x, y]
18412      */
18413     getXY: function() {
18414         if (!this.xy) {
18415             // same for XY
18416             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
18417         }
18418         return this.xy;
18419     },
18420
18421     /**
18422      * Gets the target for the event.
18423      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18424      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18425      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18426      * @return {HTMLElement}
18427      */
18428     getTarget : function(selector, maxDepth, returnEl){
18429         if (selector) {
18430             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
18431         }
18432         return returnEl ? Ext.get(this.target) : this.target;
18433     },
18434
18435     /**
18436      * Gets the related target.
18437      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
18438      * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
18439      * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
18440      * @return {HTMLElement}
18441      */
18442     getRelatedTarget : function(selector, maxDepth, returnEl){
18443         if (selector) {
18444             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
18445         }
18446         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
18447     },
18448
18449     /**
18450      * Correctly scales a given wheel delta.
18451      * @param {Number} delta The delta value.
18452      */
18453     correctWheelDelta : function (delta) {
18454         var scale = this.WHEEL_SCALE,
18455             ret = Math.round(delta / scale);
18456
18457         if (!ret && delta) {
18458             ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
18459         }
18460
18461         return ret;
18462     },
18463
18464     /**
18465      * Returns the mouse wheel deltas for this event.
18466      * @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
18467      */
18468     getWheelDeltas : function () {
18469         var me = this,
18470             event = me.browserEvent,
18471             dx = 0, dy = 0; // the deltas
18472
18473         if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
18474             dx = event.wheelDeltaX;
18475             dy = event.wheelDeltaY;
18476         } else if (event.wheelDelta) { // old WebKit and IE
18477             dy = event.wheelDelta;
18478         } else if (event.detail) { // Gecko
18479             dy = -event.detail; // gecko is backwards
18480
18481             // Gecko sometimes returns really big values if the user changes settings to
18482             // scroll a whole page per scroll
18483             if (dy > 100) {
18484                 dy = 3;
18485             } else if (dy < -100) {
18486                 dy = -3;
18487             }
18488
18489             // Firefox 3.1 adds an axis field to the event to indicate direction of
18490             // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
18491             if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
18492                 dx = dy;
18493                 dy = 0;
18494             }
18495         }
18496
18497         return {
18498             x: me.correctWheelDelta(dx),
18499             y: me.correctWheelDelta(dy)
18500         };
18501     },
18502
18503     /**
18504      * Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
18505      * {@link #getWheelDeltas} instead.
18506      * @return {Number} The mouse wheel y-delta
18507      */
18508     getWheelDelta : function(){
18509         var deltas = this.getWheelDeltas();
18510
18511         return deltas.y;
18512     },
18513
18514     /**
18515      * 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.
18516      * Example usage:<pre><code>
18517 // Handle click on any child of an element
18518 Ext.getBody().on('click', function(e){
18519     if(e.within('some-el')){
18520         alert('Clicked on a child of some-el!');
18521     }
18522 });
18523
18524 // Handle click directly on an element, ignoring clicks on child nodes
18525 Ext.getBody().on('click', function(e,t){
18526     if((t.id == 'some-el') && !e.within(t, true)){
18527         alert('Clicked directly on some-el!');
18528     }
18529 });
18530 </code></pre>
18531      * @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
18532      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
18533      * @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
18534      * @return {Boolean}
18535      */
18536     within : function(el, related, allowEl){
18537         if(el){
18538             var t = related ? this.getRelatedTarget() : this.getTarget(),
18539                 result;
18540
18541             if (t) {
18542                 result = Ext.fly(el).contains(t);
18543                 if (!result && allowEl) {
18544                     result = t == Ext.getDom(el);
18545                 }
18546                 return result;
18547             }
18548         }
18549         return false;
18550     },
18551
18552     /**
18553      * Checks if the key pressed was a "navigation" key
18554      * @return {Boolean} True if the press is a navigation keypress
18555      */
18556     isNavKeyPress : function(){
18557         var me = this,
18558             k = this.normalizeKey(me.keyCode);
18559
18560        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
18561        k == me.RETURN ||
18562        k == me.TAB ||
18563        k == me.ESC;
18564     },
18565
18566     /**
18567      * Checks if the key pressed was a "special" key
18568      * @return {Boolean} True if the press is a special keypress
18569      */
18570     isSpecialKey : function(){
18571         var k = this.normalizeKey(this.keyCode);
18572         return (this.type == 'keypress' && this.ctrlKey) ||
18573         this.isNavKeyPress() ||
18574         (k == this.BACKSPACE) || // Backspace
18575         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
18576         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
18577     },
18578
18579     /**
18580      * Returns a point object that consists of the object coordinates.
18581      * @return {Ext.util.Point} point
18582      */
18583     getPoint : function(){
18584         var xy = this.getXY();
18585         return Ext.create('Ext.util.Point', xy[0], xy[1]);
18586     },
18587
18588    /**
18589     * Returns true if the control, meta, shift or alt key was pressed during this event.
18590     * @return {Boolean}
18591     */
18592     hasModifier : function(){
18593         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
18594     },
18595
18596     /**
18597      * Injects a DOM event using the data in this object and (optionally) a new target.
18598      * This is a low-level technique and not likely to be used by application code. The
18599      * currently supported event types are:
18600      * <p><b>HTMLEvents</b></p>
18601      * <ul>
18602      * <li>load</li>
18603      * <li>unload</li>
18604      * <li>select</li>
18605      * <li>change</li>
18606      * <li>submit</li>
18607      * <li>reset</li>
18608      * <li>resize</li>
18609      * <li>scroll</li>
18610      * </ul>
18611      * <p><b>MouseEvents</b></p>
18612      * <ul>
18613      * <li>click</li>
18614      * <li>dblclick</li>
18615      * <li>mousedown</li>
18616      * <li>mouseup</li>
18617      * <li>mouseover</li>
18618      * <li>mousemove</li>
18619      * <li>mouseout</li>
18620      * </ul>
18621      * <p><b>UIEvents</b></p>
18622      * <ul>
18623      * <li>focusin</li>
18624      * <li>focusout</li>
18625      * <li>activate</li>
18626      * <li>focus</li>
18627      * <li>blur</li>
18628      * </ul>
18629      * @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
18630      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
18631      * is used to determine the target.
18632      */
18633     injectEvent: function () {
18634         var API,
18635             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
18636
18637         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
18638
18639         // IE9 has createEvent, but this code causes major problems with htmleditor (it
18640         // blocks all mouse events and maybe more). TODO
18641
18642         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
18643             API = {
18644                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
18645                     var event = doc.createEvent('HTMLEvents');
18646
18647                     event.initEvent(type, bubbles, cancelable);
18648                     return event;
18649                 },
18650
18651                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
18652                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
18653                                             button, relatedTarget) {
18654                     var event = doc.createEvent('MouseEvents'),
18655                         view = doc.defaultView || window;
18656
18657                     if (event.initMouseEvent) {
18658                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
18659                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
18660                                     shiftKey, metaKey, button, relatedTarget);
18661                     } else { // old Safari
18662                         event = doc.createEvent('UIEvents');
18663                         event.initEvent(type, bubbles, cancelable);
18664                         event.view = view;
18665                         event.detail = detail;
18666                         event.screenX = clientX;
18667                         event.screenY = clientY;
18668                         event.clientX = clientX;
18669                         event.clientY = clientY;
18670                         event.ctrlKey = ctrlKey;
18671                         event.altKey = altKey;
18672                         event.metaKey = metaKey;
18673                         event.shiftKey = shiftKey;
18674                         event.button = button;
18675                         event.relatedTarget = relatedTarget;
18676                     }
18677
18678                     return event;
18679                 },
18680
18681                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
18682                     var event = doc.createEvent('UIEvents'),
18683                         view = doc.defaultView || window;
18684
18685                     event.initUIEvent(type, bubbles, cancelable, view, detail);
18686                     return event;
18687                 },
18688
18689                 fireEvent: function (target, type, event) {
18690                     target.dispatchEvent(event);
18691                 },
18692
18693                 fixTarget: function (target) {
18694                     // Safari3 doesn't have window.dispatchEvent()
18695                     if (target == window && !target.dispatchEvent) {
18696                         return document;
18697                     }
18698
18699                     return target;
18700                 }
18701             };
18702         } else if (document.createEventObject) { // else if (IE)
18703             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
18704
18705             API = {
18706                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
18707                     var event = doc.createEventObject();
18708                     event.bubbles = bubbles;
18709                     event.cancelable = cancelable;
18710                     return event;
18711                 },
18712
18713                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
18714                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
18715                                             button, relatedTarget) {
18716                     var event = doc.createEventObject();
18717                     event.bubbles = bubbles;
18718                     event.cancelable = cancelable;
18719                     event.detail = detail;
18720                     event.screenX = clientX;
18721                     event.screenY = clientY;
18722                     event.clientX = clientX;
18723                     event.clientY = clientY;
18724                     event.ctrlKey = ctrlKey;
18725                     event.altKey = altKey;
18726                     event.shiftKey = shiftKey;
18727                     event.metaKey = metaKey;
18728                     event.button = crazyIEButtons[button] || button;
18729                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
18730                     return event;
18731                 },
18732
18733                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
18734                     var event = doc.createEventObject();
18735                     event.bubbles = bubbles;
18736                     event.cancelable = cancelable;
18737                     return event;
18738                 },
18739
18740                 fireEvent: function (target, type, event) {
18741                     target.fireEvent('on' + type, event);
18742                 },
18743
18744                 fixTarget: function (target) {
18745                     if (target == document) {
18746                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
18747                         // IE6,IE7 cannot properly call document.fireEvent()
18748                         return document.documentElement;
18749                     }
18750
18751                     return target;
18752                 }
18753             };
18754         }
18755
18756         //----------------
18757         // HTMLEvents
18758
18759         Ext.Object.each({
18760                 load:   [false, false],
18761                 unload: [false, false],
18762                 select: [true, false],
18763                 change: [true, false],
18764                 submit: [true, true],
18765                 reset:  [true, false],
18766                 resize: [true, false],
18767                 scroll: [true, false]
18768             },
18769             function (name, value) {
18770                 var bubbles = value[0], cancelable = value[1];
18771                 dispatchers[name] = function (targetEl, srcEvent) {
18772                     var e = API.createHtmlEvent(name, bubbles, cancelable);
18773                     API.fireEvent(targetEl, name, e);
18774                 };
18775             });
18776
18777         //----------------
18778         // MouseEvents
18779
18780         function createMouseEventDispatcher (type, detail) {
18781             var cancelable = (type != 'mousemove');
18782             return function (targetEl, srcEvent) {
18783                 var xy = srcEvent.getXY(),
18784                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
18785                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
18786                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
18787                                 srcEvent.relatedTarget);
18788                 API.fireEvent(targetEl, type, e);
18789             };
18790         }
18791
18792         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
18793             function (eventName) {
18794                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
18795             });
18796
18797         //----------------
18798         // UIEvents
18799
18800         Ext.Object.each({
18801                 focusin:  [true, false],
18802                 focusout: [true, false],
18803                 activate: [true, true],
18804                 focus:    [false, false],
18805                 blur:     [false, false]
18806             },
18807             function (name, value) {
18808                 var bubbles = value[0], cancelable = value[1];
18809                 dispatchers[name] = function (targetEl, srcEvent) {
18810                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
18811                     API.fireEvent(targetEl, name, e);
18812                 };
18813             });
18814
18815         //---------
18816         if (!API) {
18817             // not even sure what ancient browsers fall into this category...
18818
18819             dispatchers = {}; // never mind all those we just built :P
18820
18821             API = {
18822                 fixTarget: function (t) {
18823                     return t;
18824                 }
18825             };
18826         }
18827
18828         function cannotInject (target, srcEvent) {
18829         }
18830
18831         return function (target) {
18832             var me = this,
18833                 dispatcher = dispatchers[me.type] || cannotInject,
18834                 t = target ? (target.dom || target) : me.getTarget();
18835
18836             t = API.fixTarget(t);
18837             dispatcher(t, me);
18838         };
18839     }() // call to produce method
18840
18841 }, function() {
18842
18843 Ext.EventObject = new Ext.EventObjectImpl();
18844
18845 });
18846
18847
18848 /**
18849  * @class Ext.Element
18850  */
18851 (function(){
18852     var doc = document,
18853         activeElement = null,
18854         isCSS1 = doc.compatMode == "CSS1Compat",
18855         ELEMENT = Ext.Element,
18856         fly = function(el){
18857             if (!_fly) {
18858                 _fly = new Ext.Element.Flyweight();
18859             }
18860             _fly.dom = el;
18861             return _fly;
18862         }, _fly;
18863
18864     // If the browser does not support document.activeElement we need some assistance.
18865     // This covers old Safari 3.2 (4.0 added activeElement along with just about all
18866     // other browsers). We need this support to handle issues with old Safari.
18867     if (!('activeElement' in doc) && doc.addEventListener) {
18868         doc.addEventListener('focus',
18869             function (ev) {
18870                 if (ev && ev.target) {
18871                     activeElement = (ev.target == doc) ? null : ev.target;
18872                 }
18873             }, true);
18874     }
18875
18876     /*
18877      * Helper function to create the function that will restore the selection.
18878      */
18879     function makeSelectionRestoreFn (activeEl, start, end) {
18880         return function () {
18881             activeEl.selectionStart = start;
18882             activeEl.selectionEnd = end;
18883         };
18884     }
18885
18886     Ext.apply(ELEMENT, {
18887         isAncestor : function(p, c) {
18888             var ret = false;
18889
18890             p = Ext.getDom(p);
18891             c = Ext.getDom(c);
18892             if (p && c) {
18893                 if (p.contains) {
18894                     return p.contains(c);
18895                 } else if (p.compareDocumentPosition) {
18896                     return !!(p.compareDocumentPosition(c) & 16);
18897                 } else {
18898                     while ((c = c.parentNode)) {
18899                         ret = c == p || ret;
18900                     }
18901                 }
18902             }
18903             return ret;
18904         },
18905
18906         /**
18907          * Returns the active element in the DOM. If the browser supports activeElement
18908          * on the document, this is returned. If not, the focus is tracked and the active
18909          * element is maintained internally.
18910          * @return {HTMLElement} The active (focused) element in the document.
18911          */
18912         getActiveElement: function () {
18913             return doc.activeElement || activeElement;
18914         },
18915
18916         /**
18917          * Creates a function to call to clean up problems with the work-around for the
18918          * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
18919          * the element before calling getComputedStyle and then to restore its original
18920          * display value. The problem with this is that it corrupts the selection of an
18921          * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
18922          * To cleanup after this, we need to capture the selection of any such element and
18923          * then restore it after we have restored the display style.
18924          *
18925          * @param target {Element} The top-most element being adjusted.
18926          * @private
18927          */
18928         getRightMarginFixCleaner: function (target) {
18929             var supports = Ext.supports,
18930                 hasInputBug = supports.DisplayChangeInputSelectionBug,
18931                 hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug;
18932
18933             if (hasInputBug || hasTextAreaBug) {
18934                 var activeEl = doc.activeElement || activeElement, // save a call
18935                     tag = activeEl && activeEl.tagName,
18936                     start,
18937                     end;
18938
18939                 if ((hasTextAreaBug && tag == 'TEXTAREA') ||
18940                     (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
18941                     if (ELEMENT.isAncestor(target, activeEl)) {
18942                         start = activeEl.selectionStart;
18943                         end = activeEl.selectionEnd;
18944
18945                         if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
18946                             // We don't create the raw closure here inline because that
18947                             // will be costly even if we don't want to return it (nested
18948                             // function decls and exprs are often instantiated on entry
18949                             // regardless of whether execution ever reaches them):
18950                             return makeSelectionRestoreFn(activeEl, start, end);
18951                         }
18952                     }
18953                 }
18954             }
18955
18956             return Ext.emptyFn; // avoid special cases, just return a nop
18957         },
18958
18959         getViewWidth : function(full) {
18960             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
18961         },
18962
18963         getViewHeight : function(full) {
18964             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
18965         },
18966
18967         getDocumentHeight: function() {
18968             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
18969         },
18970
18971         getDocumentWidth: function() {
18972             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
18973         },
18974
18975         getViewportHeight: function(){
18976             return Ext.isIE ?
18977                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
18978                    self.innerHeight;
18979         },
18980
18981         getViewportWidth : function() {
18982             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
18983                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
18984         },
18985
18986         getY : function(el) {
18987             return ELEMENT.getXY(el)[1];
18988         },
18989
18990         getX : function(el) {
18991             return ELEMENT.getXY(el)[0];
18992         },
18993
18994         getOffsetParent: function (el) {
18995             el = Ext.getDom(el);
18996             try {
18997                 // accessing offsetParent can throw "Unspecified Error" in IE6-8 (not 9)
18998                 return el.offsetParent;
18999             } catch (e) {
19000                 var body = document.body; // safe bet, unless...
19001                 return (el == body) ? null : body;
19002             }
19003         },
19004
19005         getXY : function(el) {
19006             var p,
19007                 pe,
19008                 b,
19009                 bt,
19010                 bl,
19011                 dbd,
19012                 x = 0,
19013                 y = 0,
19014                 scroll,
19015                 hasAbsolute,
19016                 bd = (doc.body || doc.documentElement),
19017                 ret;
19018
19019             el = Ext.getDom(el);
19020
19021             if(el != bd){
19022                 hasAbsolute = fly(el).isStyle("position", "absolute");
19023
19024                 if (el.getBoundingClientRect) {
19025                     try {
19026                         b = el.getBoundingClientRect();
19027                         scroll = fly(document).getScroll();
19028                         ret = [ Math.round(b.left + scroll.left), Math.round(b.top + scroll.top) ];
19029                     } catch (e) {
19030                         // IE6-8 can also throw from getBoundingClientRect...
19031                     }
19032                 }
19033
19034                 if (!ret) {
19035                     for (p = el; p; p = ELEMENT.getOffsetParent(p)) {
19036                         pe = fly(p);
19037                         x += p.offsetLeft;
19038                         y += p.offsetTop;
19039
19040                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
19041
19042                         if (Ext.isGecko) {
19043                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
19044                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
19045
19046                             if (p != el && !pe.isStyle('overflow','visible')) {
19047                                 x += bl;
19048                                 y += bt;
19049                             }
19050                         }
19051                     }
19052
19053                     if (Ext.isSafari && hasAbsolute) {
19054                         x -= bd.offsetLeft;
19055                         y -= bd.offsetTop;
19056                     }
19057
19058                     if (Ext.isGecko && !hasAbsolute) {
19059                         dbd = fly(bd);
19060                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
19061                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
19062                     }
19063
19064                     p = el.parentNode;
19065                     while (p && p != bd) {
19066                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
19067                             x -= p.scrollLeft;
19068                             y -= p.scrollTop;
19069                         }
19070                         p = p.parentNode;
19071                     }
19072                     ret = [x,y];
19073                 }
19074             }
19075             return ret || [0,0];
19076         },
19077
19078         setXY : function(el, xy) {
19079             (el = Ext.fly(el, '_setXY')).position();
19080
19081             var pts = el.translatePoints(xy),
19082                 style = el.dom.style,
19083                 pos;
19084
19085             for (pos in pts) {
19086                 if (!isNaN(pts[pos])) {
19087                     style[pos] = pts[pos] + "px";
19088                 }
19089             }
19090         },
19091
19092         setX : function(el, x) {
19093             ELEMENT.setXY(el, [x, false]);
19094         },
19095
19096         setY : function(el, y) {
19097             ELEMENT.setXY(el, [false, y]);
19098         },
19099
19100         /**
19101          * Serializes a DOM form into a url encoded string
19102          * @param {Object} form The form
19103          * @return {String} The url encoded form
19104          */
19105         serializeForm: function(form) {
19106             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
19107                 hasSubmit = false,
19108                 encoder = encodeURIComponent,
19109                 name,
19110                 data = '',
19111                 type,
19112                 hasValue;
19113
19114             Ext.each(fElements, function(element){
19115                 name = element.name;
19116                 type = element.type;
19117
19118                 if (!element.disabled && name) {
19119                     if (/select-(one|multiple)/i.test(type)) {
19120                         Ext.each(element.options, function(opt){
19121                             if (opt.selected) {
19122                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
19123                                 data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
19124                             }
19125                         });
19126                     } else if (!(/file|undefined|reset|button/i.test(type))) {
19127                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
19128                             data += encoder(name) + '=' + encoder(element.value) + '&';
19129                             hasSubmit = /submit/i.test(type);
19130                         }
19131                     }
19132                 }
19133             });
19134             return data.substr(0, data.length - 1);
19135         }
19136     });
19137 })();
19138
19139 /**
19140  * @class Ext.Element
19141  */
19142
19143 Ext.Element.addMethods((function(){
19144     var focusRe = /button|input|textarea|select|object/;
19145     return {
19146         /**
19147          * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
19148          * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
19149          * back in, the function is not called.
19150          * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
19151          * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
19152          * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
19153          * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
19154 // Hide the menu if the mouse moves out for 250ms or more
19155 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
19156
19157 ...
19158 // Remove mouseleave monitor on menu destroy
19159 this.menuEl.un(this.mouseLeaveMonitor);
19160     </code></pre>
19161          */
19162         monitorMouseLeave: function(delay, handler, scope) {
19163             var me = this,
19164                 timer,
19165                 listeners = {
19166                     mouseleave: function(e) {
19167                         timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
19168                     },
19169                     mouseenter: function() {
19170                         clearTimeout(timer);
19171                     },
19172                     freezeEvent: true
19173                 };
19174
19175             me.on(listeners);
19176             return listeners;
19177         },
19178
19179         /**
19180          * Stops the specified event(s) from bubbling and optionally prevents the default action
19181          * @param {String/String[]} eventName an event / array of events to stop from bubbling
19182          * @param {Boolean} preventDefault (optional) true to prevent the default action too
19183          * @return {Ext.Element} this
19184          */
19185         swallowEvent : function(eventName, preventDefault) {
19186             var me = this;
19187             function fn(e) {
19188                 e.stopPropagation();
19189                 if (preventDefault) {
19190                     e.preventDefault();
19191                 }
19192             }
19193
19194             if (Ext.isArray(eventName)) {
19195                 Ext.each(eventName, function(e) {
19196                      me.on(e, fn);
19197                 });
19198                 return me;
19199             }
19200             me.on(eventName, fn);
19201             return me;
19202         },
19203
19204         /**
19205          * Create an event handler on this element such that when the event fires and is handled by this element,
19206          * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
19207          * @param {String} eventName The type of event to relay
19208          * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
19209          * for firing the relayed event
19210          */
19211         relayEvent : function(eventName, observable) {
19212             this.on(eventName, function(e) {
19213                 observable.fireEvent(eventName, e);
19214             });
19215         },
19216
19217         /**
19218          * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
19219          * @param {Boolean} forceReclean (optional) By default the element
19220          * keeps track if it has been cleaned already so
19221          * you can call this over and over. However, if you update the element and
19222          * need to force a reclean, you can pass true.
19223          */
19224         clean : function(forceReclean) {
19225             var me  = this,
19226                 dom = me.dom,
19227                 n   = dom.firstChild,
19228                 nx,
19229                 ni  = -1;
19230     
19231             if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
19232                 return me;
19233             }
19234
19235             while (n) {
19236                 nx = n.nextSibling;
19237                 if (n.nodeType == 3) {
19238                     // Remove empty/whitespace text nodes
19239                     if (!(/\S/.test(n.nodeValue))) {
19240                         dom.removeChild(n);
19241                     // Combine adjacent text nodes
19242                     } else if (nx && nx.nodeType == 3) {
19243                         n.appendData(Ext.String.trim(nx.data));
19244                         dom.removeChild(nx);
19245                         nx = n.nextSibling;
19246                         n.nodeIndex = ++ni;
19247                     }
19248                 } else {
19249                     // Recursively clean
19250                     Ext.fly(n).clean();
19251                     n.nodeIndex = ++ni;
19252                 }
19253                 n = nx;
19254             }
19255
19256             Ext.Element.data(dom, 'isCleaned', true);
19257             return me;
19258         },
19259
19260         /**
19261          * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
19262          * parameter as {@link Ext.ElementLoader#load}
19263          * @return {Ext.Element} this
19264          */
19265         load : function(options) {
19266             this.getLoader().load(options);
19267             return this;
19268         },
19269
19270         /**
19271         * Gets this element's {@link Ext.ElementLoader ElementLoader}
19272         * @return {Ext.ElementLoader} The loader
19273         */
19274         getLoader : function() {
19275             var dom = this.dom,
19276                 data = Ext.Element.data,
19277                 loader = data(dom, 'loader');
19278     
19279             if (!loader) {
19280                 loader = Ext.create('Ext.ElementLoader', {
19281                     target: this
19282                 });
19283                 data(dom, 'loader', loader);
19284             }
19285             return loader;
19286         },
19287
19288         /**
19289         * Update the innerHTML of this element, optionally searching for and processing scripts
19290         * @param {String} html The new HTML
19291         * @param {Boolean} [loadScripts=false] True to look for and process scripts
19292         * @param {Function} [callback] For async script loading you can be notified when the update completes
19293         * @return {Ext.Element} this
19294          */
19295         update : function(html, loadScripts, callback) {
19296             var me = this,
19297                 id,
19298                 dom,
19299                 interval;
19300
19301             if (!me.dom) {
19302                 return me;
19303             }
19304             html = html || '';
19305             dom = me.dom;
19306
19307             if (loadScripts !== true) {
19308                 dom.innerHTML = html;
19309                 Ext.callback(callback, me);
19310                 return me;
19311             }
19312
19313             id  = Ext.id();
19314             html += '<span id="' + id + '"></span>';
19315
19316             interval = setInterval(function(){
19317                 if (!document.getElementById(id)) {
19318                     return false;
19319                 }
19320                 clearInterval(interval);
19321                 var DOC    = document,
19322                     hd     = DOC.getElementsByTagName("head")[0],
19323                     re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
19324                     srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
19325                     typeRe = /\stype=([\'\"])(.*?)\1/i,
19326                     match,
19327                     attrs,
19328                     srcMatch,
19329                     typeMatch,
19330                     el,
19331                     s;
19332
19333                 while ((match = re.exec(html))) {
19334                     attrs = match[1];
19335                     srcMatch = attrs ? attrs.match(srcRe) : false;
19336                     if (srcMatch && srcMatch[2]) {
19337                        s = DOC.createElement("script");
19338                        s.src = srcMatch[2];
19339                        typeMatch = attrs.match(typeRe);
19340                        if (typeMatch && typeMatch[2]) {
19341                            s.type = typeMatch[2];
19342                        }
19343                        hd.appendChild(s);
19344                     } else if (match[2] && match[2].length > 0) {
19345                         if (window.execScript) {
19346                            window.execScript(match[2]);
19347                         } else {
19348                            window.eval(match[2]);
19349                         }
19350                     }
19351                 }
19352
19353                 el = DOC.getElementById(id);
19354                 if (el) {
19355                     Ext.removeNode(el);
19356                 }
19357                 Ext.callback(callback, me);
19358             }, 20);
19359             dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
19360             return me;
19361         },
19362
19363         // inherit docs, overridden so we can add removeAnchor
19364         removeAllListeners : function() {
19365             this.removeAnchor();
19366             Ext.EventManager.removeAll(this.dom);
19367             return this;
19368         },
19369     
19370         /**
19371          * Gets the parent node of the current element taking into account Ext.scopeResetCSS
19372          * @protected
19373          * @return {HTMLElement} The parent element
19374          */
19375         getScopeParent: function(){
19376             var parent = this.dom.parentNode;
19377             return Ext.scopeResetCSS ? parent.parentNode : parent;
19378         },
19379
19380         /**
19381          * Creates a proxy element of this element
19382          * @param {String/Object} config The class name of the proxy element or a DomHelper config object
19383          * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
19384          * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
19385          * @return {Ext.Element} The new proxy element
19386          */
19387         createProxy : function(config, renderTo, matchBox) {
19388             config = (typeof config == 'object') ? config : {tag : "div", cls: config};
19389
19390             var me = this,
19391                 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
19392                                    Ext.DomHelper.insertBefore(me.dom, config, true);
19393
19394             proxy.setVisibilityMode(Ext.Element.DISPLAY);
19395             proxy.hide();
19396             if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
19397                proxy.setBox(me.getBox());
19398             }
19399             return proxy;
19400         },
19401     
19402         /**
19403          * Checks whether this element can be focused.
19404          * @return {Boolean} True if the element is focusable
19405          */
19406         focusable: function(){
19407             var dom = this.dom,
19408                 nodeName = dom.nodeName.toLowerCase(),
19409                 canFocus = false,
19410                 hasTabIndex = !isNaN(dom.tabIndex);
19411             
19412             if (!dom.disabled) {
19413                 if (focusRe.test(nodeName)) {
19414                     canFocus = true;
19415                 } else {
19416                     canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
19417                 }
19418             }
19419             return canFocus && this.isVisible(true);
19420         }    
19421     };
19422 })());
19423 Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;
19424
19425 /**
19426  * @class Ext.Element
19427  */
19428 Ext.Element.addMethods({
19429     /**
19430      * Gets the x,y coordinates specified by the anchor position on the element.
19431      * @param {String} [anchor='c'] The specified anchor position.  See {@link #alignTo}
19432      * for details on supported anchor positions.
19433      * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
19434      * of page coordinates
19435      * @param {Object} [size] An object containing the size to use for calculating anchor position
19436      * {width: (target width), height: (target height)} (defaults to the element's current size)
19437      * @return {Number[]} [x, y] An array containing the element's x and y coordinates
19438      */
19439     getAnchorXY : function(anchor, local, s){
19440         //Passing a different size is useful for pre-calculating anchors,
19441         //especially for anchored animations that change the el size.
19442         anchor = (anchor || "tl").toLowerCase();
19443         s = s || {};
19444
19445         var me = this,
19446             vp = me.dom == document.body || me.dom == document,
19447             w = s.width || vp ? Ext.Element.getViewWidth() : me.getWidth(),
19448             h = s.height || vp ? Ext.Element.getViewHeight() : me.getHeight(),
19449             xy,
19450             r = Math.round,
19451             o = me.getXY(),
19452             scroll = me.getScroll(),
19453             extraX = vp ? scroll.left : !local ? o[0] : 0,
19454             extraY = vp ? scroll.top : !local ? o[1] : 0,
19455             hash = {
19456                 c  : [r(w * 0.5), r(h * 0.5)],
19457                 t  : [r(w * 0.5), 0],
19458                 l  : [0, r(h * 0.5)],
19459                 r  : [w, r(h * 0.5)],
19460                 b  : [r(w * 0.5), h],
19461                 tl : [0, 0],
19462                 bl : [0, h],
19463                 br : [w, h],
19464                 tr : [w, 0]
19465             };
19466
19467         xy = hash[anchor];
19468         return [xy[0] + extraX, xy[1] + extraY];
19469     },
19470
19471     /**
19472      * Anchors an element to another element and realigns it when the window is resized.
19473      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19474      * @param {String} position The position to align to.
19475      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19476      * @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
19477      * @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
19478      * is a number, it is used as the buffer delay (defaults to 50ms).
19479      * @param {Function} [callback] The function to call after the animation finishes
19480      * @return {Ext.Element} this
19481      */
19482     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
19483         var me = this,
19484             dom = me.dom,
19485             scroll = !Ext.isEmpty(monitorScroll),
19486             action = function(){
19487                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
19488                 Ext.callback(callback, Ext.fly(dom));
19489             },
19490             anchor = this.getAnchor();
19491
19492         // previous listener anchor, remove it
19493         this.removeAnchor();
19494         Ext.apply(anchor, {
19495             fn: action,
19496             scroll: scroll
19497         });
19498
19499         Ext.EventManager.onWindowResize(action, null);
19500
19501         if(scroll){
19502             Ext.EventManager.on(window, 'scroll', action, null,
19503                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
19504         }
19505         action.call(me); // align immediately
19506         return me;
19507     },
19508
19509     /**
19510      * Remove any anchor to this element. See {@link #anchorTo}.
19511      * @return {Ext.Element} this
19512      */
19513     removeAnchor : function(){
19514         var me = this,
19515             anchor = this.getAnchor();
19516
19517         if(anchor && anchor.fn){
19518             Ext.EventManager.removeResizeListener(anchor.fn);
19519             if(anchor.scroll){
19520                 Ext.EventManager.un(window, 'scroll', anchor.fn);
19521             }
19522             delete anchor.fn;
19523         }
19524         return me;
19525     },
19526
19527     // private
19528     getAnchor : function(){
19529         var data = Ext.Element.data,
19530             dom = this.dom;
19531             if (!dom) {
19532                 return;
19533             }
19534             var anchor = data(dom, '_anchor');
19535
19536         if(!anchor){
19537             anchor = data(dom, '_anchor', {});
19538         }
19539         return anchor;
19540     },
19541
19542     getAlignVector: function(el, spec, offset) {
19543         var me = this,
19544             side = {t:"top", l:"left", r:"right", b: "bottom"},
19545             thisRegion = me.getRegion(),
19546             elRegion;
19547
19548         el = Ext.get(el);
19549         if(!el || !el.dom){
19550         }
19551
19552         elRegion = el.getRegion();
19553     },
19554
19555     /**
19556      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
19557      * supported position values.
19558      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19559      * @param {String} [position="tl-bl?"] The position to align to (defaults to )
19560      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19561      * @return {Number[]} [x, y]
19562      */
19563     getAlignToXY : function(el, p, o){
19564         el = Ext.get(el);
19565
19566         if(!el || !el.dom){
19567         }
19568
19569         o = o || [0,0];
19570         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
19571
19572         var me = this,
19573             d = me.dom,
19574             a1,
19575             a2,
19576             x,
19577             y,
19578             //constrain the aligned el to viewport if necessary
19579             w,
19580             h,
19581             r,
19582             dw = Ext.Element.getViewWidth() -10, // 10px of margin for ie
19583             dh = Ext.Element.getViewHeight()-10, // 10px of margin for ie
19584             p1y,
19585             p1x,
19586             p2y,
19587             p2x,
19588             swapY,
19589             swapX,
19590             doc = document,
19591             docElement = doc.documentElement,
19592             docBody = doc.body,
19593             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
19594             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
19595             c = false, //constrain to viewport
19596             p1 = "",
19597             p2 = "",
19598             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
19599
19600         if(!m){
19601         }
19602
19603         p1 = m[1];
19604         p2 = m[2];
19605         c = !!m[3];
19606
19607         //Subtract the aligned el's internal xy from the target's offset xy
19608         //plus custom offset to get the aligned el's new offset xy
19609         a1 = me.getAnchorXY(p1, true);
19610         a2 = el.getAnchorXY(p2, false);
19611
19612         x = a2[0] - a1[0] + o[0];
19613         y = a2[1] - a1[1] + o[1];
19614
19615         if(c){
19616            w = me.getWidth();
19617            h = me.getHeight();
19618            r = el.getRegion();
19619            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
19620            //perpendicular to the vp border, allow the aligned el to slide on that border,
19621            //otherwise swap the aligned el to the opposite border of the target.
19622            p1y = p1.charAt(0);
19623            p1x = p1.charAt(p1.length-1);
19624            p2y = p2.charAt(0);
19625            p2x = p2.charAt(p2.length-1);
19626            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
19627            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
19628
19629
19630            if (x + w > dw + scrollX) {
19631                 x = swapX ? r.left-w : dw+scrollX-w;
19632            }
19633            if (x < scrollX) {
19634                x = swapX ? r.right : scrollX;
19635            }
19636            if (y + h > dh + scrollY) {
19637                 y = swapY ? r.top-h : dh+scrollY-h;
19638             }
19639            if (y < scrollY){
19640                y = swapY ? r.bottom : scrollY;
19641            }
19642         }
19643         return [x,y];
19644     },
19645
19646     /**
19647      * Aligns this element with another element relative to the specified anchor points. If the other element is the
19648      * document it aligns it to the viewport.
19649      * The position parameter is optional, and can be specified in any one of the following formats:
19650      * <ul>
19651      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
19652      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
19653      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
19654      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
19655      *   <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
19656      *       element's anchor point, and the second value is used as the target's anchor point.</li>
19657      * </ul>
19658      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
19659      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
19660      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
19661      * that specified in order to enforce the viewport constraints.
19662      * Following are all of the supported anchor positions:
19663 <pre>
19664 Value  Description
19665 -----  -----------------------------
19666 tl     The top left corner (default)
19667 t      The center of the top edge
19668 tr     The top right corner
19669 l      The center of the left edge
19670 c      In the center of the element
19671 r      The center of the right edge
19672 bl     The bottom left corner
19673 b      The center of the bottom edge
19674 br     The bottom right corner
19675 </pre>
19676 Example Usage:
19677 <pre><code>
19678 // align el to other-el using the default positioning ("tl-bl", non-constrained)
19679 el.alignTo("other-el");
19680
19681 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
19682 el.alignTo("other-el", "tr?");
19683
19684 // align the bottom right corner of el with the center left edge of other-el
19685 el.alignTo("other-el", "br-l?");
19686
19687 // align the center of el with the bottom left corner of other-el and
19688 // adjust the x position by -6 pixels (and the y position by 0)
19689 el.alignTo("other-el", "c-bl", [-6, 0]);
19690 </code></pre>
19691      * @param {String/HTMLElement/Ext.Element} element The element to align to.
19692      * @param {String} [position="tl-bl?"] The position to align to
19693      * @param {Number[]} [offsets] Offset the positioning by [x, y]
19694      * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
19695      * @return {Ext.Element} this
19696      */
19697     alignTo : function(element, position, offsets, animate){
19698         var me = this;
19699         return me.setXY(me.getAlignToXY(element, position, offsets),
19700                         me.anim && !!animate ? me.anim(animate) : false);
19701     },
19702
19703     // private ==>  used outside of core
19704     adjustForConstraints : function(xy, parent) {
19705         var vector = this.getConstrainVector(parent, xy);
19706         if (vector) {
19707             xy[0] += vector[0];
19708             xy[1] += vector[1];
19709         }
19710         return xy;
19711     },
19712
19713     /**
19714      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
19715      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
19716      * <p>Priority is given to constraining the top and left within the constraint.</p>
19717      * <p>The constraint may either be an existing element into which this element is to be constrained, or
19718      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
19719      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
19720      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
19721      * of using this Element's current position;
19722      * @returns {Number[]/Boolean} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
19723      * vector by which this element must be translated. Otherwise, <code>false</code>.
19724      */
19725     getConstrainVector: function(constrainTo, proposedPosition) {
19726         if (!(constrainTo instanceof Ext.util.Region)) {
19727             constrainTo = Ext.get(constrainTo).getViewRegion();
19728         }
19729         var thisRegion = this.getRegion(),
19730             vector = [0, 0],
19731             shadowSize = this.shadow && this.shadow.offset,
19732             overflowed = false;
19733
19734         // Shift this region to occupy the proposed position
19735         if (proposedPosition) {
19736             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
19737         }
19738
19739         // Reduce the constrain region to allow for shadow
19740         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
19741         if (shadowSize) {
19742             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
19743         }
19744
19745         // Constrain the X coordinate by however much this Element overflows
19746         if (thisRegion.right > constrainTo.right) {
19747             overflowed = true;
19748             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
19749         }
19750         if (thisRegion.left + vector[0] < constrainTo.left) {
19751             overflowed = true;
19752             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
19753         }
19754
19755         // Constrain the Y coordinate by however much this Element overflows
19756         if (thisRegion.bottom > constrainTo.bottom) {
19757             overflowed = true;
19758             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
19759         }
19760         if (thisRegion.top + vector[1] < constrainTo.top) {
19761             overflowed = true;
19762             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
19763         }
19764         return overflowed ? vector : false;
19765     },
19766
19767     /**
19768     * Calculates the x, y to center this element on the screen
19769     * @return {Number[]} The x, y values [x, y]
19770     */
19771     getCenterXY : function(){
19772         return this.getAlignToXY(document, 'c-c');
19773     },
19774
19775     /**
19776     * Centers the Element in either the viewport, or another Element.
19777     * @param {String/HTMLElement/Ext.Element} centerIn (optional) The element in which to center the element.
19778     */
19779     center : function(centerIn){
19780         return this.alignTo(centerIn || document, 'c-c');
19781     }
19782 });
19783
19784 /**
19785  * @class Ext.Element
19786  */
19787 (function(){
19788
19789 var ELEMENT = Ext.Element,
19790     LEFT = "left",
19791     RIGHT = "right",
19792     TOP = "top",
19793     BOTTOM = "bottom",
19794     POSITION = "position",
19795     STATIC = "static",
19796     RELATIVE = "relative",
19797     AUTO = "auto",
19798     ZINDEX = "z-index";
19799
19800 Ext.override(Ext.Element, {
19801     /**
19802       * 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).
19803       * @return {Number} The X position of the element
19804       */
19805     getX : function(){
19806         return ELEMENT.getX(this.dom);
19807     },
19808
19809     /**
19810       * 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).
19811       * @return {Number} The Y position of the element
19812       */
19813     getY : function(){
19814         return ELEMENT.getY(this.dom);
19815     },
19816
19817     /**
19818       * 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).
19819       * @return {Number[]} The XY position of the element
19820       */
19821     getXY : function(){
19822         return ELEMENT.getXY(this.dom);
19823     },
19824
19825     /**
19826       * 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.
19827       * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
19828       * @return {Number[]} The XY page offsets (e.g. [100, -200])
19829       */
19830     getOffsetsTo : function(el){
19831         var o = this.getXY(),
19832             e = Ext.fly(el, '_internal').getXY();
19833         return [o[0]-e[0],o[1]-e[1]];
19834     },
19835
19836     /**
19837      * 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).
19838      * @param {Number} The X position of the element
19839      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19840      * @return {Ext.Element} this
19841      */
19842     setX : function(x, animate){
19843         return this.setXY([x, this.getY()], animate);
19844     },
19845
19846     /**
19847      * 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).
19848      * @param {Number} The Y position of the element
19849      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19850      * @return {Ext.Element} this
19851      */
19852     setY : function(y, animate){
19853         return this.setXY([this.getX(), y], animate);
19854     },
19855
19856     /**
19857      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
19858      * @param {String} left The left CSS property value
19859      * @return {Ext.Element} this
19860      */
19861     setLeft : function(left){
19862         this.setStyle(LEFT, this.addUnits(left));
19863         return this;
19864     },
19865
19866     /**
19867      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
19868      * @param {String} top The top CSS property value
19869      * @return {Ext.Element} this
19870      */
19871     setTop : function(top){
19872         this.setStyle(TOP, this.addUnits(top));
19873         return this;
19874     },
19875
19876     /**
19877      * Sets the element's CSS right style.
19878      * @param {String} right The right CSS property value
19879      * @return {Ext.Element} this
19880      */
19881     setRight : function(right){
19882         this.setStyle(RIGHT, this.addUnits(right));
19883         return this;
19884     },
19885
19886     /**
19887      * Sets the element's CSS bottom style.
19888      * @param {String} bottom The bottom CSS property value
19889      * @return {Ext.Element} this
19890      */
19891     setBottom : function(bottom){
19892         this.setStyle(BOTTOM, this.addUnits(bottom));
19893         return this;
19894     },
19895
19896     /**
19897      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19898      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19899      * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
19900      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19901      * @return {Ext.Element} this
19902      */
19903     setXY: function(pos, animate) {
19904         var me = this;
19905         if (!animate || !me.anim) {
19906             ELEMENT.setXY(me.dom, pos);
19907         }
19908         else {
19909             if (!Ext.isObject(animate)) {
19910                 animate = {};
19911             }
19912             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
19913         }
19914         return me;
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} x X value for new position (coordinates are page-based)
19921      * @param {Number} y Y value for new position (coordinates are page-based)
19922      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19923      * @return {Ext.Element} this
19924      */
19925     setLocation : function(x, y, animate){
19926         return this.setXY([x, y], animate);
19927     },
19928
19929     /**
19930      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19931      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19932      * @param {Number} x X value for new position (coordinates are page-based)
19933      * @param {Number} y Y value for new position (coordinates are page-based)
19934      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19935      * @return {Ext.Element} this
19936      */
19937     moveTo : function(x, y, animate){
19938         return this.setXY([x, y], animate);
19939     },
19940
19941     /**
19942      * Gets the left X coordinate
19943      * @param {Boolean} local True to get the local css position instead of page coordinate
19944      * @return {Number}
19945      */
19946     getLeft : function(local){
19947         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
19948     },
19949
19950     /**
19951      * Gets the right X coordinate of the element (element X position + element width)
19952      * @param {Boolean} local True to get the local css position instead of page coordinate
19953      * @return {Number}
19954      */
19955     getRight : function(local){
19956         var me = this;
19957         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
19958     },
19959
19960     /**
19961      * Gets the top Y coordinate
19962      * @param {Boolean} local True to get the local css position instead of page coordinate
19963      * @return {Number}
19964      */
19965     getTop : function(local) {
19966         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
19967     },
19968
19969     /**
19970      * Gets the bottom Y coordinate of the element (element Y position + element height)
19971      * @param {Boolean} local True to get the local css position instead of page coordinate
19972      * @return {Number}
19973      */
19974     getBottom : function(local){
19975         var me = this;
19976         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
19977     },
19978
19979     /**
19980     * Initializes positioning on this element. If a desired position is not passed, it will make the
19981     * the element positioned relative IF it is not already positioned.
19982     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
19983     * @param {Number} zIndex (optional) The zIndex to apply
19984     * @param {Number} x (optional) Set the page X position
19985     * @param {Number} y (optional) Set the page Y position
19986     */
19987     position : function(pos, zIndex, x, y) {
19988         var me = this;
19989
19990         if (!pos && me.isStyle(POSITION, STATIC)){
19991             me.setStyle(POSITION, RELATIVE);
19992         } else if(pos) {
19993             me.setStyle(POSITION, pos);
19994         }
19995         if (zIndex){
19996             me.setStyle(ZINDEX, zIndex);
19997         }
19998         if (x || y) {
19999             me.setXY([x || false, y || false]);
20000         }
20001     },
20002
20003     /**
20004     * Clear positioning back to the default when the document was loaded
20005     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
20006     * @return {Ext.Element} this
20007      */
20008     clearPositioning : function(value){
20009         value = value || '';
20010         this.setStyle({
20011             left : value,
20012             right : value,
20013             top : value,
20014             bottom : value,
20015             "z-index" : "",
20016             position : STATIC
20017         });
20018         return this;
20019     },
20020
20021     /**
20022     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
20023     * snapshot before performing an update and then restoring the element.
20024     * @return {Object}
20025     */
20026     getPositioning : function(){
20027         var l = this.getStyle(LEFT);
20028         var t = this.getStyle(TOP);
20029         return {
20030             "position" : this.getStyle(POSITION),
20031             "left" : l,
20032             "right" : l ? "" : this.getStyle(RIGHT),
20033             "top" : t,
20034             "bottom" : t ? "" : this.getStyle(BOTTOM),
20035             "z-index" : this.getStyle(ZINDEX)
20036         };
20037     },
20038
20039     /**
20040     * Set positioning with an object returned by getPositioning().
20041     * @param {Object} posCfg
20042     * @return {Ext.Element} this
20043      */
20044     setPositioning : function(pc){
20045         var me = this,
20046             style = me.dom.style;
20047
20048         me.setStyle(pc);
20049
20050         if(pc.right == AUTO){
20051             style.right = "";
20052         }
20053         if(pc.bottom == AUTO){
20054             style.bottom = "";
20055         }
20056
20057         return me;
20058     },
20059
20060     /**
20061      * Translates the passed page coordinates into left/top css values for this element
20062      * @param {Number/Number[]} x The page x or an array containing [x, y]
20063      * @param {Number} y (optional) The page y, required if x is not an array
20064      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
20065      */
20066     translatePoints: function(x, y) {
20067         if (Ext.isArray(x)) {
20068              y = x[1];
20069              x = x[0];
20070         }
20071         var me = this,
20072             relative = me.isStyle(POSITION, RELATIVE),
20073             o = me.getXY(),
20074             left = parseInt(me.getStyle(LEFT), 10),
20075             top = parseInt(me.getStyle(TOP), 10);
20076
20077         if (!Ext.isNumber(left)) {
20078             left = relative ? 0 : me.dom.offsetLeft;
20079         }
20080         if (!Ext.isNumber(top)) {
20081             top = relative ? 0 : me.dom.offsetTop;
20082         }
20083         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
20084         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
20085         return {
20086             left: left,
20087             top: top
20088         };
20089     },
20090
20091     /**
20092      * 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.
20093      * @param {Object} box The box to fill {x, y, width, height}
20094      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
20095      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20096      * @return {Ext.Element} this
20097      */
20098     setBox: function(box, adjust, animate) {
20099         var me = this,
20100             w = box.width,
20101             h = box.height;
20102         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
20103             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
20104             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
20105         }
20106         me.setBounds(box.x, box.y, w, h, animate);
20107         return me;
20108     },
20109
20110     /**
20111      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20112      * set another Element's size/location to match this element.
20113      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
20114      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
20115      * @return {Object} box An object in the format<pre><code>
20116 {
20117     x: &lt;Element's X position>,
20118     y: &lt;Element's Y position>,
20119     width: &lt;Element's width>,
20120     height: &lt;Element's height>,
20121     bottom: &lt;Element's lower bound>,
20122     right: &lt;Element's rightmost bound>
20123 }
20124 </code></pre>
20125      * The returned object may also be addressed as an Array where index 0 contains the X position
20126      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20127      */
20128     getBox: function(contentBox, local) {
20129         var me = this,
20130             xy,
20131             left,
20132             top,
20133             getBorderWidth = me.getBorderWidth,
20134             getPadding = me.getPadding,
20135             l, r, t, b, w, h, bx;
20136         if (!local) {
20137             xy = me.getXY();
20138         } else {
20139             left = parseInt(me.getStyle("left"), 10) || 0;
20140             top = parseInt(me.getStyle("top"), 10) || 0;
20141             xy = [left, top];
20142         }
20143         w = me.getWidth();
20144         h = me.getHeight();
20145         if (!contentBox) {
20146             bx = {
20147                 x: xy[0],
20148                 y: xy[1],
20149                 0: xy[0],
20150                 1: xy[1],
20151                 width: w,
20152                 height: h
20153             };
20154         } else {
20155             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
20156             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
20157             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
20158             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
20159             bx = {
20160                 x: xy[0] + l,
20161                 y: xy[1] + t,
20162                 0: xy[0] + l,
20163                 1: xy[1] + t,
20164                 width: w - (l + r),
20165                 height: h - (t + b)
20166             };
20167         }
20168         bx.right = bx.x + bx.width;
20169         bx.bottom = bx.y + bx.height;
20170         return bx;
20171     },
20172
20173     /**
20174      * Move this element relative to its current position.
20175      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20176      * @param {Number} distance How far to move the element in pixels
20177      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20178      */
20179     move: function(direction, distance, animate) {
20180         var me = this,
20181             xy = me.getXY(),
20182             x = xy[0],
20183             y = xy[1],
20184             left = [x - distance, y],
20185             right = [x + distance, y],
20186             top = [x, y - distance],
20187             bottom = [x, y + distance],
20188             hash = {
20189                 l: left,
20190                 left: left,
20191                 r: right,
20192                 right: right,
20193                 t: top,
20194                 top: top,
20195                 up: top,
20196                 b: bottom,
20197                 bottom: bottom,
20198                 down: bottom
20199             };
20200
20201         direction = direction.toLowerCase();
20202         me.moveTo(hash[direction][0], hash[direction][1], animate);
20203     },
20204
20205     /**
20206      * Quick set left and top adding default units
20207      * @param {String} left The left CSS property value
20208      * @param {String} top The top CSS property value
20209      * @return {Ext.Element} this
20210      */
20211     setLeftTop: function(left, top) {
20212         var me = this,
20213             style = me.dom.style;
20214         style.left = me.addUnits(left);
20215         style.top = me.addUnits(top);
20216         return me;
20217     },
20218
20219     /**
20220      * Returns the region of this element.
20221      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
20222      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20223      */
20224     getRegion: function() {
20225         return this.getPageBox(true);
20226     },
20227
20228     /**
20229      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
20230      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
20231      */
20232     getViewRegion: function() {
20233         var me = this,
20234             isBody = me.dom === document.body,
20235             scroll, pos, top, left, width, height;
20236
20237         // For the body we want to do some special logic
20238         if (isBody) {
20239             scroll = me.getScroll();
20240             left = scroll.left;
20241             top = scroll.top;
20242             width = Ext.Element.getViewportWidth();
20243             height = Ext.Element.getViewportHeight();
20244         }
20245         else {
20246             pos = me.getXY();
20247             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
20248             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
20249             width = me.getWidth(true);
20250             height = me.getHeight(true);
20251         }
20252
20253         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
20254     },
20255
20256     /**
20257      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
20258      * set another Element's size/location to match this element.
20259      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
20260      * @return {Object} box An object in the format<pre><code>
20261 {
20262     x: &lt;Element's X position>,
20263     y: &lt;Element's Y position>,
20264     width: &lt;Element's width>,
20265     height: &lt;Element's height>,
20266     bottom: &lt;Element's lower bound>,
20267     right: &lt;Element's rightmost bound>
20268 }
20269 </code></pre>
20270      * The returned object may also be addressed as an Array where index 0 contains the X position
20271      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
20272      */
20273     getPageBox : function(getRegion) {
20274         var me = this,
20275             el = me.dom,
20276             isDoc = el === document.body,
20277             w = isDoc ? Ext.Element.getViewWidth()  : el.offsetWidth,
20278             h = isDoc ? Ext.Element.getViewHeight() : el.offsetHeight,
20279             xy = me.getXY(),
20280             t = xy[1],
20281             r = xy[0] + w,
20282             b = xy[1] + h,
20283             l = xy[0];
20284
20285         if (getRegion) {
20286             return Ext.create('Ext.util.Region', t, r, b, l);
20287         }
20288         else {
20289             return {
20290                 left: l,
20291                 top: t,
20292                 width: w,
20293                 height: h,
20294                 right: r,
20295                 bottom: b
20296             };
20297         }
20298     },
20299
20300     /**
20301      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
20302      * @param {Number} x X value for new position (coordinates are page-based)
20303      * @param {Number} y Y value for new position (coordinates are page-based)
20304      * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
20305      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20306      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
20307      * </ul></div>
20308      * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
20309      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
20310      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
20311      * </ul></div>
20312      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20313      * @return {Ext.Element} this
20314      */
20315     setBounds: function(x, y, width, height, animate) {
20316         var me = this;
20317         if (!animate || !me.anim) {
20318             me.setSize(width, height);
20319             me.setLocation(x, y);
20320         } else {
20321             if (!Ext.isObject(animate)) {
20322                 animate = {};
20323             }
20324             me.animate(Ext.applyIf({
20325                 to: {
20326                     x: x,
20327                     y: y,
20328                     width: me.adjustWidth(width),
20329                     height: me.adjustHeight(height)
20330                 }
20331             }, animate));
20332         }
20333         return me;
20334     },
20335
20336     /**
20337      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
20338      * @param {Ext.util.Region} region The region to fill
20339      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20340      * @return {Ext.Element} this
20341      */
20342     setRegion: function(region, animate) {
20343         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
20344     }
20345 });
20346 })();
20347
20348 /**
20349  * @class Ext.Element
20350  */
20351 Ext.override(Ext.Element, {
20352     /**
20353      * Returns true if this element is scrollable.
20354      * @return {Boolean}
20355      */
20356     isScrollable : function(){
20357         var dom = this.dom;
20358         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
20359     },
20360
20361     /**
20362      * Returns the current scroll position of the element.
20363      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
20364      */
20365     getScroll : function() {
20366         var d = this.dom, 
20367             doc = document,
20368             body = doc.body,
20369             docElement = doc.documentElement,
20370             l,
20371             t,
20372             ret;
20373
20374         if (d == doc || d == body) {
20375             if (Ext.isIE && Ext.isStrict) {
20376                 l = docElement.scrollLeft; 
20377                 t = docElement.scrollTop;
20378             } else {
20379                 l = window.pageXOffset;
20380                 t = window.pageYOffset;
20381             }
20382             ret = {
20383                 left: l || (body ? body.scrollLeft : 0), 
20384                 top : t || (body ? body.scrollTop : 0)
20385             };
20386         } else {
20387             ret = {
20388                 left: d.scrollLeft, 
20389                 top : d.scrollTop
20390             };
20391         }
20392         
20393         return ret;
20394     },
20395     
20396     /**
20397      * 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().
20398      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
20399      * @param {Number} value The new scroll value
20400      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20401      * @return {Ext.Element} this
20402      */
20403     scrollTo : function(side, value, animate) {
20404         //check if we're scrolling top or left
20405         var top = /top/i.test(side),
20406             me = this,
20407             dom = me.dom,
20408             obj = {},
20409             prop;
20410         if (!animate || !me.anim) {
20411             // just setting the value, so grab the direction
20412             prop = 'scroll' + (top ? 'Top' : 'Left');
20413             dom[prop] = value;
20414         }
20415         else {
20416             if (!Ext.isObject(animate)) {
20417                 animate = {};
20418             }
20419             obj['scroll' + (top ? 'Top' : 'Left')] = value;
20420             me.animate(Ext.applyIf({
20421                 to: obj
20422             }, animate));
20423         }
20424         return me;
20425     },
20426
20427     /**
20428      * Scrolls this element into view within the passed container.
20429      * @param {String/HTMLElement/Ext.Element} container (optional) The container element to scroll (defaults to document.body).  Should be a
20430      * string (id), dom node, or Ext.Element.
20431      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
20432      * @return {Ext.Element} this
20433      */
20434     scrollIntoView : function(container, hscroll) {
20435         container = Ext.getDom(container) || Ext.getBody().dom;
20436         var el = this.dom,
20437             offsets = this.getOffsetsTo(container),
20438             // el's box
20439             left = offsets[0] + container.scrollLeft,
20440             top = offsets[1] + container.scrollTop,
20441             bottom = top + el.offsetHeight,
20442             right = left + el.offsetWidth,
20443             // ct's box
20444             ctClientHeight = container.clientHeight,
20445             ctScrollTop = parseInt(container.scrollTop, 10),
20446             ctScrollLeft = parseInt(container.scrollLeft, 10),
20447             ctBottom = ctScrollTop + ctClientHeight,
20448             ctRight = ctScrollLeft + container.clientWidth;
20449
20450         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
20451             container.scrollTop = top;
20452         } else if (bottom > ctBottom) {
20453             container.scrollTop = bottom - ctClientHeight;
20454         }
20455         // corrects IE, other browsers will ignore
20456         container.scrollTop = container.scrollTop;
20457
20458         if (hscroll !== false) {
20459             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
20460                 container.scrollLeft = left;
20461             }
20462             else if (right > ctRight) {
20463                 container.scrollLeft = right - container.clientWidth;
20464             }
20465             container.scrollLeft = container.scrollLeft;
20466         }
20467         return this;
20468     },
20469
20470     // private
20471     scrollChildIntoView : function(child, hscroll) {
20472         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
20473     },
20474
20475     /**
20476      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
20477      * within this element's scrollable range.
20478      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
20479      * @param {Number} distance How far to scroll the element in pixels
20480      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
20481      * @return {Boolean} Returns true if a scroll was triggered or false if the element
20482      * was scrolled as far as it could go.
20483      */
20484      scroll : function(direction, distance, animate) {
20485         if (!this.isScrollable()) {
20486             return false;
20487         }
20488         var el = this.dom,
20489             l = el.scrollLeft, t = el.scrollTop,
20490             w = el.scrollWidth, h = el.scrollHeight,
20491             cw = el.clientWidth, ch = el.clientHeight,
20492             scrolled = false, v,
20493             hash = {
20494                 l: Math.min(l + distance, w-cw),
20495                 r: v = Math.max(l - distance, 0),
20496                 t: Math.max(t - distance, 0),
20497                 b: Math.min(t + distance, h-ch)
20498             };
20499             hash.d = hash.b;
20500             hash.u = hash.t;
20501
20502         direction = direction.substr(0, 1);
20503         if ((v = hash[direction]) > -1) {
20504             scrolled = true;
20505             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
20506         }
20507         return scrolled;
20508     }
20509 });
20510 /**
20511  * @class Ext.Element
20512  */
20513 Ext.Element.addMethods(
20514     function() {
20515         var VISIBILITY      = "visibility",
20516             DISPLAY         = "display",
20517             HIDDEN          = "hidden",
20518             NONE            = "none",
20519             XMASKED         = Ext.baseCSSPrefix + "masked",
20520             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
20521             data            = Ext.Element.data;
20522
20523         return {
20524             /**
20525              * Checks whether the element is currently visible using both visibility and display properties.
20526              * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden
20527              * @return {Boolean} True if the element is currently visible, else false
20528              */
20529             isVisible : function(deep) {
20530                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
20531                     p   = this.dom.parentNode;
20532
20533                 if (deep !== true || !vis) {
20534                     return vis;
20535                 }
20536
20537                 while (p && !(/^body/i.test(p.tagName))) {
20538                     if (!Ext.fly(p, '_isVisible').isVisible()) {
20539                         return false;
20540                     }
20541                     p = p.parentNode;
20542                 }
20543                 return true;
20544             },
20545
20546             /**
20547              * Returns true if display is not "none"
20548              * @return {Boolean}
20549              */
20550             isDisplayed : function() {
20551                 return !this.isStyle(DISPLAY, NONE);
20552             },
20553
20554             /**
20555              * Convenience method for setVisibilityMode(Element.DISPLAY)
20556              * @param {String} display (optional) What to set display to when visible
20557              * @return {Ext.Element} this
20558              */
20559             enableDisplayMode : function(display) {
20560                 this.setVisibilityMode(Ext.Element.DISPLAY);
20561
20562                 if (!Ext.isEmpty(display)) {
20563                     data(this.dom, 'originalDisplay', display);
20564                 }
20565
20566                 return this;
20567             },
20568
20569             /**
20570              * Puts a mask over this element to disable user interaction. Requires core.css.
20571              * This method can only be applied to elements which accept child nodes.
20572              * @param {String} msg (optional) A message to display in the mask
20573              * @param {String} msgCls (optional) A css class to apply to the msg element
20574              * @return {Ext.Element} The mask element
20575              */
20576             mask : function(msg, msgCls) {
20577                 var me  = this,
20578                     dom = me.dom,
20579                     setExpression = dom.style.setExpression,
20580                     dh  = Ext.DomHelper,
20581                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
20582                     el,
20583                     mask;
20584
20585                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
20586                     me.addCls(XMASKEDRELATIVE);
20587                 }
20588                 el = data(dom, 'maskMsg');
20589                 if (el) {
20590                     el.remove();
20591                 }
20592                 el = data(dom, 'mask');
20593                 if (el) {
20594                     el.remove();
20595                 }
20596
20597                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
20598                 data(dom, 'mask', mask);
20599
20600                 me.addCls(XMASKED);
20601                 mask.setDisplayed(true);
20602
20603                 if (typeof msg == 'string') {
20604                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
20605                     data(dom, 'maskMsg', mm);
20606                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
20607                     mm.dom.firstChild.innerHTML = msg;
20608                     mm.setDisplayed(true);
20609                     mm.center(me);
20610                 }
20611                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
20612                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
20613                 // In normal use cases an element will be masked for a limited period of time.
20614                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
20615                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
20616                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
20617                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
20618                 }
20619
20620                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
20621                 // Different versions from those which make the same error for width!
20622                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
20623                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
20624                 }
20625                 // ie will not expand full height automatically
20626                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
20627                     mask.setSize(undefined, me.getHeight());
20628                 }
20629                 return mask;
20630             },
20631
20632             /**
20633              * Removes a previously applied mask.
20634              */
20635             unmask : function() {
20636                 var me      = this,
20637                     dom     = me.dom,
20638                     mask    = data(dom, 'mask'),
20639                     maskMsg = data(dom, 'maskMsg');
20640
20641                 if (mask) {
20642                     // Remove resource-intensive CSS expressions as soon as they are not required.
20643                     if (mask.dom.style.clearExpression) {
20644                         mask.dom.style.clearExpression('width');
20645                         mask.dom.style.clearExpression('height');
20646                     }
20647                     if (maskMsg) {
20648                         maskMsg.remove();
20649                         data(dom, 'maskMsg', undefined);
20650                     }
20651
20652                     mask.remove();
20653                     data(dom, 'mask', undefined);
20654                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
20655                 }
20656             },
20657             /**
20658              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
20659              * @return {Boolean}
20660              */
20661             isMasked : function() {
20662                 var me = this,
20663                     mask = data(me.dom, 'mask'),
20664                     maskMsg = data(me.dom, 'maskMsg');
20665
20666                 if (mask && mask.isVisible()) {
20667                     if (maskMsg) {
20668                         maskMsg.center(me);
20669                     }
20670                     return true;
20671                 }
20672                 return false;
20673             },
20674
20675             /**
20676              * Creates an iframe shim for this element to keep selects and other windowed objects from
20677              * showing through.
20678              * @return {Ext.Element} The new shim element
20679              */
20680             createShim : function() {
20681                 var el = document.createElement('iframe'),
20682                     shim;
20683
20684                 el.frameBorder = '0';
20685                 el.className = Ext.baseCSSPrefix + 'shim';
20686                 el.src = Ext.SSL_SECURE_URL;
20687                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
20688                 shim.autoBoxAdjust = false;
20689                 return shim;
20690             }
20691         };
20692     }()
20693 );
20694 /**
20695  * @class Ext.Element
20696  */
20697 Ext.Element.addMethods({
20698     /**
20699      * Convenience method for constructing a KeyMap
20700      * @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:
20701      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
20702      * @param {Function} fn The function to call
20703      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
20704      * @return {Ext.util.KeyMap} The KeyMap created
20705      */
20706     addKeyListener : function(key, fn, scope){
20707         var config;
20708         if(typeof key != 'object' || Ext.isArray(key)){
20709             config = {
20710                 key: key,
20711                 fn: fn,
20712                 scope: scope
20713             };
20714         }else{
20715             config = {
20716                 key : key.key,
20717                 shift : key.shift,
20718                 ctrl : key.ctrl,
20719                 alt : key.alt,
20720                 fn: fn,
20721                 scope: scope
20722             };
20723         }
20724         return Ext.create('Ext.util.KeyMap', this, config);
20725     },
20726
20727     /**
20728      * Creates a KeyMap for this element
20729      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
20730      * @return {Ext.util.KeyMap} The KeyMap created
20731      */
20732     addKeyMap : function(config){
20733         return Ext.create('Ext.util.KeyMap', this, config);
20734     }
20735 });
20736
20737 //Import the newly-added Ext.Element functions into CompositeElementLite. We call this here because
20738 //Element.keys.js is the last extra Ext.Element include in the ext-all.js build
20739 Ext.CompositeElementLite.importElementMethods();
20740
20741 /**
20742  * @class Ext.CompositeElementLite
20743  */
20744 Ext.apply(Ext.CompositeElementLite.prototype, {
20745     addElements : function(els, root){
20746         if(!els){
20747             return this;
20748         }
20749         if(typeof els == "string"){
20750             els = Ext.Element.selectorFunction(els, root);
20751         }
20752         var yels = this.elements;
20753         Ext.each(els, function(e) {
20754             yels.push(Ext.get(e));
20755         });
20756         return this;
20757     },
20758
20759     /**
20760      * Returns the first Element
20761      * @return {Ext.Element}
20762      */
20763     first : function(){
20764         return this.item(0);
20765     },
20766
20767     /**
20768      * Returns the last Element
20769      * @return {Ext.Element}
20770      */
20771     last : function(){
20772         return this.item(this.getCount()-1);
20773     },
20774
20775     /**
20776      * Returns true if this composite contains the passed element
20777      * @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.
20778      * @return Boolean
20779      */
20780     contains : function(el){
20781         return this.indexOf(el) != -1;
20782     },
20783
20784     /**
20785     * Removes the specified element(s).
20786     * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
20787     * or an array of any of those.
20788     * @param {Boolean} removeDom (optional) True to also remove the element from the document
20789     * @return {Ext.CompositeElement} this
20790     */
20791     removeElement : function(keys, removeDom){
20792         var me = this,
20793             els = this.elements,
20794             el;
20795         Ext.each(keys, function(val){
20796             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
20797                 if(removeDom){
20798                     if(el.dom){
20799                         el.remove();
20800                     }else{
20801                         Ext.removeNode(el);
20802                     }
20803                 }
20804                 Ext.Array.erase(els, val, 1);
20805             }
20806         });
20807         return this;
20808     }
20809 });
20810
20811 /**
20812  * @class Ext.CompositeElement
20813  * @extends Ext.CompositeElementLite
20814  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
20815  * members, or to perform collective actions upon the whole set.</p>
20816  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
20817  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
20818  * <p>All methods return <i>this</i> and can be chained.</p>
20819  * Usage:
20820 <pre><code>
20821 var els = Ext.select("#some-el div.some-class", true);
20822 // or select directly from an existing element
20823 var el = Ext.get('some-el');
20824 el.select('div.some-class', true);
20825
20826 els.setWidth(100); // all elements become 100 width
20827 els.hide(true); // all elements fade out and hide
20828 // or
20829 els.setWidth(100).hide(true);
20830 </code></pre>
20831  */
20832 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
20833
20834     constructor : function(els, root){
20835         this.elements = [];
20836         this.add(els, root);
20837     },
20838
20839     // private
20840     getElement : function(el){
20841         // In this case just return it, since we already have a reference to it
20842         return el;
20843     },
20844
20845     // private
20846     transformElement : function(el){
20847         return Ext.get(el);
20848     }
20849 });
20850
20851 /**
20852  * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
20853  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
20854  * {@link Ext.CompositeElementLite CompositeElementLite} object.
20855  * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
20856  * @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
20857  * @param {HTMLElement/String} [root] The root element of the query or id of the root
20858  * @return {Ext.CompositeElementLite/Ext.CompositeElement}
20859  * @member Ext.Element
20860  * @method select
20861  */
20862 Ext.Element.select = function(selector, unique, root){
20863     var els;
20864     if(typeof selector == "string"){
20865         els = Ext.Element.selectorFunction(selector, root);
20866     }else if(selector.length !== undefined){
20867         els = selector;
20868     }else{
20869     }
20870     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
20871 };
20872
20873 /**
20874  * Shorthand of {@link Ext.Element#select}.
20875  * @member Ext
20876  * @method select
20877  * @alias Ext.Element#select
20878  */
20879 Ext.select = Ext.Element.select;
20880
20881
20882 /*
20883
20884 This file is part of Ext JS 4
20885
20886 Copyright (c) 2011 Sencha Inc
20887
20888 Contact:  http://www.sencha.com/contact
20889
20890 Commercial Usage
20891 Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
20892
20893 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
20894
20895 */
20896 /**
20897  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
20898  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
20899  *
20900  * For example:
20901  *
20902  *     Ext.define('Employee', {
20903  *         extend: 'Ext.util.Observable',
20904  *         constructor: function(config){
20905  *             this.name = config.name;
20906  *             this.addEvents({
20907  *                 "fired" : true,
20908  *                 "quit" : true
20909  *             });
20910  *
20911  *             // Copy configured listeners into *this* object so that the base class's
20912  *             // constructor will add them.
20913  *             this.listeners = config.listeners;
20914  *
20915  *             // Call our superclass constructor to complete construction process.
20916  *             this.callParent(arguments)
20917  *         }
20918  *     });
20919  *
20920  * This could then be used like this:
20921  *
20922  *     var newEmployee = new Employee({
20923  *         name: employeeName,
20924  *         listeners: {
20925  *             quit: function() {
20926  *                 // By default, "this" will be the object that fired the event.
20927  *                 alert(this.name + " has quit!");
20928  *             }
20929  *         }
20930  *     });
20931  */
20932 Ext.define('Ext.util.Observable', {
20933
20934     /* Begin Definitions */
20935
20936     requires: ['Ext.util.Event'],
20937
20938     statics: {
20939         /**
20940          * Removes **all** added captures from the Observable.
20941          *
20942          * @param {Ext.util.Observable} o The Observable to release
20943          * @static
20944          */
20945         releaseCapture: function(o) {
20946             o.fireEvent = this.prototype.fireEvent;
20947         },
20948
20949         /**
20950          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
20951          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
20952          * the event will not fire.
20953          *
20954          * @param {Ext.util.Observable} o The Observable to capture events from.
20955          * @param {Function} fn The function to call when an event is fired.
20956          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
20957          * the Observable firing the event.
20958          * @static
20959          */
20960         capture: function(o, fn, scope) {
20961             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
20962         },
20963
20964         /**
20965          * Sets observability on the passed class constructor.
20966          *
20967          * This makes any event fired on any instance of the passed class also fire a single event through
20968          * the **class** allowing for central handling of events on many instances at once.
20969          *
20970          * Usage:
20971          *
20972          *     Ext.util.Observable.observe(Ext.data.Connection);
20973          *     Ext.data.Connection.on('beforerequest', function(con, options) {
20974          *         console.log('Ajax request made to ' + options.url);
20975          *     });
20976          *
20977          * @param {Function} c The class constructor to make observable.
20978          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
20979          * @static
20980          */
20981         observe: function(cls, listeners) {
20982             if (cls) {
20983                 if (!cls.isObservable) {
20984                     Ext.applyIf(cls, new this());
20985                     this.capture(cls.prototype, cls.fireEvent, cls);
20986                 }
20987                 if (Ext.isObject(listeners)) {
20988                     cls.on(listeners);
20989                 }
20990                 return cls;
20991             }
20992         }
20993     },
20994
20995     /* End Definitions */
20996
20997     /**
20998      * @cfg {Object} listeners
20999      *
21000      * A config object containing one or more event handlers to be added to this object during initialization. This
21001      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
21002      * handlers at once.
21003      *
21004      * **DOM events from Ext JS {@link Ext.Component Components}**
21005      *
21006      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
21007      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
21008      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
21009      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
21010      * DOM listener to:
21011      *
21012      *     new Ext.panel.Panel({
21013      *         width: 400,
21014      *         height: 200,
21015      *         dockedItems: [{
21016      *             xtype: 'toolbar'
21017      *         }],
21018      *         listeners: {
21019      *             click: {
21020      *                 element: 'el', //bind to the underlying el property on the panel
21021      *                 fn: function(){ console.log('click el'); }
21022      *             },
21023      *             dblclick: {
21024      *                 element: 'body', //bind to the underlying body property on the panel
21025      *                 fn: function(){ console.log('dblclick body'); }
21026      *             }
21027      *         }
21028      *     });
21029      */
21030     // @private
21031     isObservable: true,
21032
21033     constructor: function(config) {
21034         var me = this;
21035
21036         Ext.apply(me, config);
21037         if (me.listeners) {
21038             me.on(me.listeners);
21039             delete me.listeners;
21040         }
21041         me.events = me.events || {};
21042
21043         if (me.bubbleEvents) {
21044             me.enableBubble(me.bubbleEvents);
21045         }
21046     },
21047
21048     // @private
21049     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
21050
21051     /**
21052      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
21053      * destroyed.
21054      *
21055      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
21056      * @param {Object/String} ename The event name, or an object containing event name properties.
21057      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21058      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21059      * in which the handler function is executed.
21060      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
21061      * {@link Ext.util.Observable#addListener addListener} options.
21062      */
21063     addManagedListener : function(item, ename, fn, scope, options) {
21064         var me = this,
21065             managedListeners = me.managedListeners = me.managedListeners || [],
21066             config;
21067
21068         if (typeof ename !== 'string') {
21069             options = ename;
21070             for (ename in options) {
21071                 if (options.hasOwnProperty(ename)) {
21072                     config = options[ename];
21073                     if (!me.eventOptionsRe.test(ename)) {
21074                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21075                     }
21076                 }
21077             }
21078         }
21079         else {
21080             managedListeners.push({
21081                 item: item,
21082                 ename: ename,
21083                 fn: fn,
21084                 scope: scope,
21085                 options: options
21086             });
21087
21088             item.on(ename, fn, scope, options);
21089         }
21090     },
21091
21092     /**
21093      * Removes listeners that were added by the {@link #mon} method.
21094      *
21095      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
21096      * @param {Object/String} ename The event name, or an object containing event name properties.
21097      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
21098      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
21099      * in which the handler function is executed.
21100      */
21101     removeManagedListener : function(item, ename, fn, scope) {
21102         var me = this,
21103             options,
21104             config,
21105             managedListeners,
21106             length,
21107             i;
21108
21109         if (typeof ename !== 'string') {
21110             options = ename;
21111             for (ename in options) {
21112                 if (options.hasOwnProperty(ename)) {
21113                     config = options[ename];
21114                     if (!me.eventOptionsRe.test(ename)) {
21115                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
21116                     }
21117                 }
21118             }
21119         }
21120
21121         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
21122
21123         for (i = 0, length = managedListeners.length; i < length; i++) {
21124             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
21125         }
21126     },
21127
21128     /**
21129      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
21130      * to {@link #addListener}).
21131      *
21132      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
21133      * calling {@link #enableBubble}.
21134      *
21135      * @param {String} eventName The name of the event to fire.
21136      * @param {Object...} args Variable number of parameters are passed to handlers.
21137      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
21138      */
21139     fireEvent: function(eventName) {
21140         var name = eventName.toLowerCase(),
21141             events = this.events,
21142             event = events && events[name],
21143             bubbles = event && event.bubble;
21144
21145         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
21146     },
21147
21148     /**
21149      * Continue to fire event.
21150      * @private
21151      *
21152      * @param {String} eventName
21153      * @param {Array} args
21154      * @param {Boolean} bubbles
21155      */
21156     continueFireEvent: function(eventName, args, bubbles) {
21157         var target = this,
21158             queue, event,
21159             ret = true;
21160
21161         do {
21162             if (target.eventsSuspended === true) {
21163                 if ((queue = target.eventQueue)) {
21164                     queue.push([eventName, args, bubbles]);
21165                 }
21166                 return ret;
21167             } else {
21168                 event = target.events[eventName];
21169                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
21170                 // configure to bubble.
21171                 if (event && event != true) {
21172                     if ((ret = event.fire.apply(event, args)) === false) {
21173                         break;
21174                     }
21175                 }
21176             }
21177         } while (bubbles && (target = target.getBubbleParent()));
21178         return ret;
21179     },
21180
21181     /**
21182      * Gets the bubbling parent for an Observable
21183      * @private
21184      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
21185      */
21186     getBubbleParent: function(){
21187         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
21188         if (parent && parent.isObservable) {
21189             return parent;
21190         }
21191         return null;
21192     },
21193
21194     /**
21195      * Appends an event handler to this object.
21196      *
21197      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
21198      * event names.
21199      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
21200      * {@link #fireEvent} plus the `options` parameter described below.
21201      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
21202      * omitted, defaults to the object which fired the event.**
21203      * @param {Object} [options] An object containing handler configuration.
21204      *
21205      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
21206      *
21207      * This object may contain any of the following properties:
21208      *
21209      * - **scope** : Object
21210      *
21211      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
21212      *   which fired the event.**
21213      *
21214      * - **delay** : Number
21215      *
21216      *   The number of milliseconds to delay the invocation of the handler after the event fires.
21217      *
21218      * - **single** : Boolean
21219      *
21220      *   True to add a handler to handle just the next firing of the event, and then remove itself.
21221      *
21222      * - **buffer** : Number
21223      *
21224      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
21225      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
21226      *   handler is scheduled in its place.
21227      *
21228      * - **target** : Observable
21229      *
21230      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
21231      *   child Observable.
21232      *
21233      * - **element** : String
21234      *
21235      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
21236      *   property which references an element to add a listener to.
21237      *
21238      *   This option is useful during Component construction to add DOM event listeners to elements of
21239      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
21240      *   For example, to add a click listener to a Panel's body:
21241      *
21242      *       new Ext.panel.Panel({
21243      *           title: 'The title',
21244      *           listeners: {
21245      *               click: this.handlePanelClick,
21246      *               element: 'body'
21247      *           }
21248      *       });
21249      *
21250      * **Combining Options**
21251      *
21252      * Using the options argument, it is possible to combine different types of listeners:
21253      *
21254      * A delayed, one-time listener.
21255      *
21256      *     myPanel.on('hide', this.handleClick, this, {
21257      *         single: true,
21258      *         delay: 100
21259      *     });
21260      *
21261      * **Attaching multiple handlers in 1 call**
21262      *
21263      * The method also allows for a single argument to be passed which is a config object containing properties which
21264      * specify multiple events. For example:
21265      *
21266      *     myGridPanel.on({
21267      *         cellClick: this.onCellClick,
21268      *         mouseover: this.onMouseOver,
21269      *         mouseout: this.onMouseOut,
21270      *         scope: this // Important. Ensure "this" is correct during handler execution
21271      *     });
21272      *
21273      * One can also specify options for each event handler separately:
21274      *
21275      *     myGridPanel.on({
21276      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
21277      *         mouseover: {fn: panel.onMouseOver, scope: panel}
21278      *     });
21279      *
21280      */
21281     addListener: function(ename, fn, scope, options) {
21282         var me = this,
21283             config,
21284             event;
21285
21286         if (typeof ename !== 'string') {
21287             options = ename;
21288             for (ename in options) {
21289                 if (options.hasOwnProperty(ename)) {
21290                     config = options[ename];
21291                     if (!me.eventOptionsRe.test(ename)) {
21292                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
21293                     }
21294                 }
21295             }
21296         }
21297         else {
21298             ename = ename.toLowerCase();
21299             me.events[ename] = me.events[ename] || true;
21300             event = me.events[ename] || true;
21301             if (Ext.isBoolean(event)) {
21302                 me.events[ename] = event = new Ext.util.Event(me, ename);
21303             }
21304             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
21305         }
21306     },
21307
21308     /**
21309      * Removes an event handler.
21310      *
21311      * @param {String} eventName The type of event the handler was associated with.
21312      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
21313      * {@link #addListener} call.**
21314      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
21315      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
21316      */
21317     removeListener: function(ename, fn, scope) {
21318         var me = this,
21319             config,
21320             event,
21321             options;
21322
21323         if (typeof ename !== 'string') {
21324             options = ename;
21325             for (ename in options) {
21326                 if (options.hasOwnProperty(ename)) {
21327                     config = options[ename];
21328                     if (!me.eventOptionsRe.test(ename)) {
21329                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
21330                     }
21331                 }
21332             }
21333         } else {
21334             ename = ename.toLowerCase();
21335             event = me.events[ename];
21336             if (event && event.isEvent) {
21337                 event.removeListener(fn, scope);
21338             }
21339         }
21340     },
21341
21342     /**
21343      * Removes all listeners for this object including the managed listeners
21344      */
21345     clearListeners: function() {
21346         var events = this.events,
21347             event,
21348             key;
21349
21350         for (key in events) {
21351             if (events.hasOwnProperty(key)) {
21352                 event = events[key];
21353                 if (event.isEvent) {
21354                     event.clearListeners();
21355                 }
21356             }
21357         }
21358
21359         this.clearManagedListeners();
21360     },
21361
21362
21363     /**
21364      * Removes all managed listeners for this object.
21365      */
21366     clearManagedListeners : function() {
21367         var managedListeners = this.managedListeners || [],
21368             i = 0,
21369             len = managedListeners.length;
21370
21371         for (; i < len; i++) {
21372             this.removeManagedListenerItem(true, managedListeners[i]);
21373         }
21374
21375         this.managedListeners = [];
21376     },
21377
21378     /**
21379      * Remove a single managed listener item
21380      * @private
21381      * @param {Boolean} isClear True if this is being called during a clear
21382      * @param {Object} managedListener The managed listener item
21383      * See removeManagedListener for other args
21384      */
21385     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
21386         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
21387             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
21388             if (!isClear) {
21389                 Ext.Array.remove(this.managedListeners, managedListener);
21390             }
21391         }
21392     },
21393
21394
21395     /**
21396      * Adds the specified events to the list of events which this Observable may fire.
21397      *
21398      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
21399      * event name string if multiple event names are being passed as separate parameters. Usage:
21400      *
21401      *     this.addEvents({
21402      *         storeloaded: true,
21403      *         storecleared: true
21404      *     });
21405      *
21406      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
21407      * parameters. Usage:
21408      *
21409      *     this.addEvents('storeloaded', 'storecleared');
21410      *
21411      */
21412     addEvents: function(o) {
21413         var me = this,
21414             args,
21415             len,
21416             i;
21417
21418             me.events = me.events || {};
21419         if (Ext.isString(o)) {
21420             args = arguments;
21421             i = args.length;
21422
21423             while (i--) {
21424                 me.events[args[i]] = me.events[args[i]] || true;
21425             }
21426         } else {
21427             Ext.applyIf(me.events, o);
21428         }
21429     },
21430
21431     /**
21432      * Checks to see if this object has any listeners for a specified event
21433      *
21434      * @param {String} eventName The name of the event to check for
21435      * @return {Boolean} True if the event is being listened for, else false
21436      */
21437     hasListener: function(ename) {
21438         var event = this.events[ename.toLowerCase()];
21439         return event && event.isEvent === true && event.listeners.length > 0;
21440     },
21441
21442     /**
21443      * Suspends the firing of all events. (see {@link #resumeEvents})
21444      *
21445      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
21446      * after the {@link #resumeEvents} call instead of discarding all suspended events.
21447      */
21448     suspendEvents: function(queueSuspended) {
21449         this.eventsSuspended = true;
21450         if (queueSuspended && !this.eventQueue) {
21451             this.eventQueue = [];
21452         }
21453     },
21454
21455     /**
21456      * Resumes firing events (see {@link #suspendEvents}).
21457      *
21458      * If events were suspended using the `queueSuspended` parameter, then all events fired
21459      * during event suspension will be sent to any listeners now.
21460      */
21461     resumeEvents: function() {
21462         var me = this,
21463             queued = me.eventQueue;
21464
21465         me.eventsSuspended = false;
21466         delete me.eventQueue;
21467
21468         if (queued) {
21469             Ext.each(queued, function(e) {
21470                 me.continueFireEvent.apply(me, e);
21471             });
21472         }
21473     },
21474
21475     /**
21476      * Relays selected events from the specified Observable as if the events were fired by `this`.
21477      *
21478      * @param {Object} origin The Observable whose events this object is to relay.
21479      * @param {String[]} events Array of event names to relay.
21480      * @param {String} prefix
21481      */
21482     relayEvents : function(origin, events, prefix) {
21483         prefix = prefix || '';
21484         var me = this,
21485             len = events.length,
21486             i = 0,
21487             oldName,
21488             newName;
21489
21490         for (; i < len; i++) {
21491             oldName = events[i].substr(prefix.length);
21492             newName = prefix + oldName;
21493             me.events[newName] = me.events[newName] || true;
21494             origin.on(oldName, me.createRelayer(newName));
21495         }
21496     },
21497
21498     /**
21499      * @private
21500      * Creates an event handling function which refires the event from this object as the passed event name.
21501      * @param newName
21502      * @returns {Function}
21503      */
21504     createRelayer: function(newName){
21505         var me = this;
21506         return function(){
21507             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
21508         };
21509     },
21510
21511     /**
21512      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
21513      * present. There is no implementation in the Observable base class.
21514      *
21515      * This is commonly used by Ext.Components to bubble events to owner Containers.
21516      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
21517      * Component's immediate owner. But if a known target is required, this can be overridden to access the
21518      * required target more quickly.
21519      *
21520      * Example:
21521      *
21522      *     Ext.override(Ext.form.field.Base, {
21523      *         //  Add functionality to Field's initComponent to enable the change event to bubble
21524      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
21525      *             this.enableBubble('change');
21526      *         }),
21527      *
21528      *         //  We know that we want Field's events to bubble directly to the FormPanel.
21529      *         getBubbleTarget : function() {
21530      *             if (!this.formPanel) {
21531      *                 this.formPanel = this.findParentByType('form');
21532      *             }
21533      *             return this.formPanel;
21534      *         }
21535      *     });
21536      *
21537      *     var myForm = new Ext.formPanel({
21538      *         title: 'User Details',
21539      *         items: [{
21540      *             ...
21541      *         }],
21542      *         listeners: {
21543      *             change: function() {
21544      *                 // Title goes red if form has been modified.
21545      *                 myForm.header.setStyle('color', 'red');
21546      *             }
21547      *         }
21548      *     });
21549      *
21550      * @param {String/String[]} events The event name to bubble, or an Array of event names.
21551      */
21552     enableBubble: function(events) {
21553         var me = this;
21554         if (!Ext.isEmpty(events)) {
21555             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
21556             Ext.each(events,
21557             function(ename) {
21558                 ename = ename.toLowerCase();
21559                 var ce = me.events[ename] || true;
21560                 if (Ext.isBoolean(ce)) {
21561                     ce = new Ext.util.Event(me, ename);
21562                     me.events[ename] = ce;
21563                 }
21564                 ce.bubble = true;
21565             });
21566         }
21567     }
21568 }, function() {
21569
21570     this.createAlias({
21571         /**
21572          * @method
21573          * Shorthand for {@link #addListener}.
21574          * @alias Ext.util.Observable#addListener
21575          */
21576         on: 'addListener',
21577         /**
21578          * @method
21579          * Shorthand for {@link #removeListener}.
21580          * @alias Ext.util.Observable#removeListener
21581          */
21582         un: 'removeListener',
21583         /**
21584          * @method
21585          * Shorthand for {@link #addManagedListener}.
21586          * @alias Ext.util.Observable#addManagedListener
21587          */
21588         mon: 'addManagedListener',
21589         /**
21590          * @method
21591          * Shorthand for {@link #removeManagedListener}.
21592          * @alias Ext.util.Observable#removeManagedListener
21593          */
21594         mun: 'removeManagedListener'
21595     });
21596
21597     //deprecated, will be removed in 5.0
21598     this.observeClass = this.observe;
21599
21600     Ext.apply(Ext.util.Observable.prototype, function(){
21601         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
21602         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
21603         // private
21604         function getMethodEvent(method){
21605             var e = (this.methodEvents = this.methodEvents || {})[method],
21606                 returnValue,
21607                 v,
21608                 cancel,
21609                 obj = this;
21610
21611             if (!e) {
21612                 this.methodEvents[method] = e = {};
21613                 e.originalFn = this[method];
21614                 e.methodName = method;
21615                 e.before = [];
21616                 e.after = [];
21617
21618                 var makeCall = function(fn, scope, args){
21619                     if((v = fn.apply(scope || obj, args)) !== undefined){
21620                         if (typeof v == 'object') {
21621                             if(v.returnValue !== undefined){
21622                                 returnValue = v.returnValue;
21623                             }else{
21624                                 returnValue = v;
21625                             }
21626                             cancel = !!v.cancel;
21627                         }
21628                         else
21629                             if (v === false) {
21630                                 cancel = true;
21631                             }
21632                             else {
21633                                 returnValue = v;
21634                             }
21635                     }
21636                 };
21637
21638                 this[method] = function(){
21639                     var args = Array.prototype.slice.call(arguments, 0),
21640                         b, i, len;
21641                     returnValue = v = undefined;
21642                     cancel = false;
21643
21644                     for(i = 0, len = e.before.length; i < len; i++){
21645                         b = e.before[i];
21646                         makeCall(b.fn, b.scope, args);
21647                         if (cancel) {
21648                             return returnValue;
21649                         }
21650                     }
21651
21652                     if((v = e.originalFn.apply(obj, args)) !== undefined){
21653                         returnValue = v;
21654                     }
21655
21656                     for(i = 0, len = e.after.length; i < len; i++){
21657                         b = e.after[i];
21658                         makeCall(b.fn, b.scope, args);
21659                         if (cancel) {
21660                             return returnValue;
21661                         }
21662                     }
21663                     return returnValue;
21664                 };
21665             }
21666             return e;
21667         }
21668
21669         return {
21670             // these are considered experimental
21671             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
21672             // adds an 'interceptor' called before the original method
21673             beforeMethod : function(method, fn, scope){
21674                 getMethodEvent.call(this, method).before.push({
21675                     fn: fn,
21676                     scope: scope
21677                 });
21678             },
21679
21680             // adds a 'sequence' called after the original method
21681             afterMethod : function(method, fn, scope){
21682                 getMethodEvent.call(this, method).after.push({
21683                     fn: fn,
21684                     scope: scope
21685                 });
21686             },
21687
21688             removeMethodListener: function(method, fn, scope){
21689                 var e = this.getMethodEvent(method),
21690                     i, len;
21691                 for(i = 0, len = e.before.length; i < len; i++){
21692                     if(e.before[i].fn == fn && e.before[i].scope == scope){
21693                         Ext.Array.erase(e.before, i, 1);
21694                         return;
21695                     }
21696                 }
21697                 for(i = 0, len = e.after.length; i < len; i++){
21698                     if(e.after[i].fn == fn && e.after[i].scope == scope){
21699                         Ext.Array.erase(e.after, i, 1);
21700                         return;
21701                     }
21702                 }
21703             },
21704
21705             toggleEventLogging: function(toggle) {
21706                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
21707                     if (Ext.isDefined(Ext.global.console)) {
21708                         Ext.global.console.log(en, arguments);
21709                     }
21710                 });
21711             }
21712         };
21713     }());
21714 });
21715
21716 /**
21717  * @class Ext.util.Animate
21718  * This animation class is a mixin.
21719  * 
21720  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
21721  * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement}, 
21722  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
21723  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
21724  * opacity (color, paddings, and margins can not be animated).
21725  * 
21726  * ## Animation Basics
21727  * 
21728  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
21729  * you wish to animate. Easing and duration are defaulted values specified below.
21730  * Easing describes how the intermediate values used during a transition will be calculated. 
21731  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
21732  * You may use the defaults for easing and duration, but you must always set a 
21733  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
21734  * 
21735  * Popular element 'to' configurations are:
21736  * 
21737  *  - opacity
21738  *  - x
21739  *  - y
21740  *  - color
21741  *  - height
21742  *  - width 
21743  * 
21744  * Popular sprite 'to' configurations are:
21745  * 
21746  *  - translation
21747  *  - path
21748  *  - scale
21749  *  - stroke
21750  *  - rotation
21751  * 
21752  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
21753  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
21754  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
21755  * 
21756  * For example, a simple animation to fade out an element with a default easing and duration:
21757  * 
21758  *     var p1 = Ext.get('myElementId');
21759  * 
21760  *     p1.animate({
21761  *         to: {
21762  *             opacity: 0
21763  *         }
21764  *     });
21765  * 
21766  * To make this animation fade out in a tenth of a second:
21767  * 
21768  *     var p1 = Ext.get('myElementId');
21769  * 
21770  *     p1.animate({
21771  *        duration: 100,
21772  *         to: {
21773  *             opacity: 0
21774  *         }
21775  *     });
21776  * 
21777  * ## Animation Queues
21778  * 
21779  * By default all animations are added to a queue which allows for animation via a chain-style API.
21780  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
21781  * 
21782  *     p1.animate({
21783  *         to: {
21784  *             x: 500
21785  *         }
21786  *     }).animate({
21787  *         to: {
21788  *             y: 150
21789  *         }
21790  *     }).animate({
21791  *         to: {
21792  *             backgroundColor: '#f00'  //red
21793  *         }
21794  *     }).animate({
21795  *         to: {
21796  *             opacity: 0
21797  *         }
21798  *     });
21799  * 
21800  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
21801  * subsequent animations for the specified target will be run concurrently (at the same time).
21802  * 
21803  *     p1.syncFx();  //this will make all animations run at the same time
21804  * 
21805  *     p1.animate({
21806  *         to: {
21807  *             x: 500
21808  *         }
21809  *     }).animate({
21810  *         to: {
21811  *             y: 150
21812  *         }
21813  *     }).animate({
21814  *         to: {
21815  *             backgroundColor: '#f00'  //red
21816  *         }
21817  *     }).animate({
21818  *         to: {
21819  *             opacity: 0
21820  *         }
21821  *     });
21822  * 
21823  * This works the same as:
21824  * 
21825  *     p1.animate({
21826  *         to: {
21827  *             x: 500,
21828  *             y: 150,
21829  *             backgroundColor: '#f00'  //red
21830  *             opacity: 0
21831  *         }
21832  *     });
21833  * 
21834  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
21835  * currently running animations and clear any queued animations. 
21836  * 
21837  * ## Animation Keyframes
21838  *
21839  * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the 
21840  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
21841  * The previous example can be written with the following syntax:
21842  * 
21843  *     p1.animate({
21844  *         duration: 1000,  //one second total
21845  *         keyframes: {
21846  *             25: {     //from 0 to 250ms (25%)
21847  *                 x: 0
21848  *             },
21849  *             50: {   //from 250ms to 500ms (50%)
21850  *                 y: 0
21851  *             },
21852  *             75: {  //from 500ms to 750ms (75%)
21853  *                 backgroundColor: '#f00'  //red
21854  *             },
21855  *             100: {  //from 750ms to 1sec
21856  *                 opacity: 0
21857  *             }
21858  *         }
21859  *     });
21860  * 
21861  * ## Animation Events
21862  * 
21863  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate}, 
21864  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
21865  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
21866  * fires for each keyframe in your animation.
21867  * 
21868  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
21869  *    
21870  *     startAnimate: function() {
21871  *         var p1 = Ext.get('myElementId');
21872  *         p1.animate({
21873  *            duration: 100,
21874  *             to: {
21875  *                 opacity: 0
21876  *             },
21877  *             listeners: {
21878  *                 beforeanimate:  function() {
21879  *                     // Execute my custom method before the animation
21880  *                     this.myBeforeAnimateFn();
21881  *                 },
21882  *                 afteranimate: function() {
21883  *                     // Execute my custom method after the animation
21884  *                     this.myAfterAnimateFn();
21885  *                 },
21886  *                 scope: this
21887  *         });
21888  *     },
21889  *     myBeforeAnimateFn: function() {
21890  *       // My custom logic
21891  *     },
21892  *     myAfterAnimateFn: function() {
21893  *       // My custom logic
21894  *     }
21895  * 
21896  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
21897  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
21898  * method.  This method will return false if there are no active animations or return the currently 
21899  * running {@link Ext.fx.Anim} instance.
21900  * 
21901  * In this example, we're going to wait for the current animation to finish, then stop any other 
21902  * queued animations before we fade our element's opacity to 0:
21903  * 
21904  *     var curAnim = p1.getActiveAnimation();
21905  *     if (curAnim) {
21906  *         curAnim.on('afteranimate', function() {
21907  *             p1.stopAnimation();
21908  *             p1.animate({
21909  *                 to: {
21910  *                     opacity: 0
21911  *                 }
21912  *             });
21913  *         });
21914  *     }
21915  * 
21916  * @docauthor Jamie Avins <jamie@sencha.com>
21917  */
21918 Ext.define('Ext.util.Animate', {
21919
21920     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
21921
21922     /**
21923      * <p>Perform custom animation on this object.<p>
21924      * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
21925      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
21926      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
21927      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
21928      * <p>Properties include<ul>
21929      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
21930      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
21931      * ths object being animated. See the sections below on Element and Component animation.<div></li>
21932      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
21933      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
21934      * <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>
21935      * <li>ease</li>
21936      * <li>easeIn</li>
21937      * <li>easeOut</li>
21938      * <li>easeInOut</li>
21939      * <li>backIn</li>
21940      * <li>backOut</li>
21941      * <li>elasticIn</li>
21942      * <li>elasticOut</li>
21943      * <li>bounceIn</li>
21944      * <li>bounceOut</li>
21945      * </ul></code></div></li>
21946      * <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.
21947      * 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>
21948      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
21949      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
21950      * </ul></p>
21951      * <h3>Animating an {@link Ext.Element Element}</h3>
21952      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21953      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
21954      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
21955      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
21956      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
21957      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
21958      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
21959      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21960      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21961      * <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>
21962      * </ul>
21963      * <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
21964      * 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
21965      * directly animate certain properties of Components.</b></p>
21966      * <h3>Animating a {@link Ext.Component Component}</h3>
21967      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21968      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
21969      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
21970      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
21971      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
21972      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21973      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21974      * <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
21975      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
21976      * </ul>
21977      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
21978      * <pre><code>
21979 myWindow = Ext.create('Ext.window.Window', {
21980     title: 'Test Component animation',
21981     width: 500,
21982     height: 300,
21983     layout: {
21984         type: 'hbox',
21985         align: 'stretch'
21986     },
21987     items: [{
21988         title: 'Left: 33%',
21989         margins: '5 0 5 5',
21990         flex: 1
21991     }, {
21992         title: 'Left: 66%',
21993         margins: '5 5 5 5',
21994         flex: 2
21995     }]
21996 });
21997 myWindow.show();
21998 myWindow.header.el.on('click', function() {
21999     myWindow.animate({
22000         to: {
22001             width: (myWindow.getWidth() == 500) ? 700 : 500,
22002             height: (myWindow.getHeight() == 300) ? 400 : 300,
22003         }
22004     });
22005 });
22006 </code></pre>
22007      * <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
22008      * 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>
22009      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
22010      * @return {Object} this
22011      */
22012     animate: function(animObj) {
22013         var me = this;
22014         if (Ext.fx.Manager.hasFxBlock(me.id)) {
22015             return me;
22016         }
22017         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
22018         return this;
22019     },
22020
22021     // @private - process the passed fx configuration.
22022     anim: function(config) {
22023         if (!Ext.isObject(config)) {
22024             return (config) ? {} : false;
22025         }
22026
22027         var me = this;
22028
22029         if (config.stopAnimation) {
22030             me.stopAnimation();
22031         }
22032
22033         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
22034
22035         return Ext.apply({
22036             target: me,
22037             paused: true
22038         }, config);
22039     },
22040
22041     /**
22042      * @deprecated 4.0 Replaced by {@link #stopAnimation}
22043      * Stops any running effects and clears this object's internal effects queue if it contains
22044      * any additional effects that haven't started yet.
22045      * @return {Ext.Element} The Element
22046      * @method
22047      */
22048     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
22049
22050     /**
22051      * Stops any running effects and clears this object's internal effects queue if it contains
22052      * any additional effects that haven't started yet.
22053      * @return {Ext.Element} The Element
22054      */
22055     stopAnimation: function() {
22056         Ext.fx.Manager.stopAnimation(this.id);
22057         return this;
22058     },
22059
22060     /**
22061      * Ensures that all effects queued after syncFx is called on this object are
22062      * run concurrently.  This is the opposite of {@link #sequenceFx}.
22063      * @return {Object} this
22064      */
22065     syncFx: function() {
22066         Ext.fx.Manager.setFxDefaults(this.id, {
22067             concurrent: true
22068         });
22069         return this;
22070     },
22071
22072     /**
22073      * Ensures that all effects queued after sequenceFx is called on this object are
22074      * run in sequence.  This is the opposite of {@link #syncFx}.
22075      * @return {Object} this
22076      */
22077     sequenceFx: function() {
22078         Ext.fx.Manager.setFxDefaults(this.id, {
22079             concurrent: false
22080         });
22081         return this;
22082     },
22083
22084     /**
22085      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
22086      * @alias Ext.util.Animate#getActiveAnimation
22087      * @method
22088      */
22089     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
22090
22091     /**
22092      * Returns the current animation if this object has any effects actively running or queued, else returns false.
22093      * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
22094      */
22095     getActiveAnimation: function() {
22096         return Ext.fx.Manager.getActiveAnimation(this.id);
22097     }
22098 }, function(){
22099     // Apply Animate mixin manually until Element is defined in the proper 4.x way
22100     Ext.applyIf(Ext.Element.prototype, this.prototype);
22101     // We need to call this again so the animation methods get copied over to CE
22102     Ext.CompositeElementLite.importElementMethods();
22103 });
22104 /**
22105  * @class Ext.state.Provider
22106  * <p>Abstract base class for state provider implementations. The provider is responsible
22107  * for setting values  and extracting values to/from the underlying storage source. The 
22108  * storage source can vary and the details should be implemented in a subclass. For example
22109  * a provider could use a server side database or the browser localstorage where supported.</p>
22110  *
22111  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
22112  * dates and defines the Provider interface. By default these methods put the value and the
22113  * type information into a delimited string that can be stored. These should be overridden in 
22114  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
22115  */
22116 Ext.define('Ext.state.Provider', {
22117     mixins: {
22118         observable: 'Ext.util.Observable'
22119     },
22120     
22121     /**
22122      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
22123      * Defaults to <tt>'ext-'</tt>
22124      */
22125     prefix: 'ext-',
22126     
22127     constructor : function(config){
22128         config = config || {};
22129         var me = this;
22130         Ext.apply(me, config);
22131         /**
22132          * @event statechange
22133          * Fires when a state change occurs.
22134          * @param {Ext.state.Provider} this This state provider
22135          * @param {String} key The state key which was changed
22136          * @param {String} value The encoded value for the state
22137          */
22138         me.addEvents("statechange");
22139         me.state = {};
22140         me.mixins.observable.constructor.call(me);
22141     },
22142     
22143     /**
22144      * Returns the current value for a key
22145      * @param {String} name The key name
22146      * @param {Object} defaultValue A default value to return if the key's value is not found
22147      * @return {Object} The state data
22148      */
22149     get : function(name, defaultValue){
22150         return typeof this.state[name] == "undefined" ?
22151             defaultValue : this.state[name];
22152     },
22153
22154     /**
22155      * Clears a value from the state
22156      * @param {String} name The key name
22157      */
22158     clear : function(name){
22159         var me = this;
22160         delete me.state[name];
22161         me.fireEvent("statechange", me, name, null);
22162     },
22163
22164     /**
22165      * Sets the value for a key
22166      * @param {String} name The key name
22167      * @param {Object} value The value to set
22168      */
22169     set : function(name, value){
22170         var me = this;
22171         me.state[name] = value;
22172         me.fireEvent("statechange", me, name, value);
22173     },
22174
22175     /**
22176      * Decodes a string previously encoded with {@link #encodeValue}.
22177      * @param {String} value The value to decode
22178      * @return {Object} The decoded value
22179      */
22180     decodeValue : function(value){
22181
22182         // a -> Array
22183         // n -> Number
22184         // d -> Date
22185         // b -> Boolean
22186         // s -> String
22187         // o -> Object
22188         // -> Empty (null)
22189
22190         var me = this,
22191             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
22192             matches = re.exec(unescape(value)),
22193             all,
22194             type,
22195             value,
22196             keyValue;
22197             
22198         if(!matches || !matches[1]){
22199             return; // non state
22200         }
22201         
22202         type = matches[1];
22203         value = matches[2];
22204         switch (type) {
22205             case 'e':
22206                 return null;
22207             case 'n':
22208                 return parseFloat(value);
22209             case 'd':
22210                 return new Date(Date.parse(value));
22211             case 'b':
22212                 return (value == '1');
22213             case 'a':
22214                 all = [];
22215                 if(value != ''){
22216                     Ext.each(value.split('^'), function(val){
22217                         all.push(me.decodeValue(val));
22218                     }, me);
22219                 }
22220                 return all;
22221            case 'o':
22222                 all = {};
22223                 if(value != ''){
22224                     Ext.each(value.split('^'), function(val){
22225                         keyValue = val.split('=');
22226                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
22227                     }, me);
22228                 }
22229                 return all;
22230            default:
22231                 return value;
22232         }
22233     },
22234
22235     /**
22236      * Encodes a value including type information.  Decode with {@link #decodeValue}.
22237      * @param {Object} value The value to encode
22238      * @return {String} The encoded value
22239      */
22240     encodeValue : function(value){
22241         var flat = '',
22242             i = 0,
22243             enc,
22244             len,
22245             key;
22246             
22247         if (value == null) {
22248             return 'e:1';    
22249         } else if(typeof value == 'number') {
22250             enc = 'n:' + value;
22251         } else if(typeof value == 'boolean') {
22252             enc = 'b:' + (value ? '1' : '0');
22253         } else if(Ext.isDate(value)) {
22254             enc = 'd:' + value.toGMTString();
22255         } else if(Ext.isArray(value)) {
22256             for (len = value.length; i < len; i++) {
22257                 flat += this.encodeValue(value[i]);
22258                 if (i != len - 1) {
22259                     flat += '^';
22260                 }
22261             }
22262             enc = 'a:' + flat;
22263         } else if (typeof value == 'object') {
22264             for (key in value) {
22265                 if (typeof value[key] != 'function' && value[key] !== undefined) {
22266                     flat += key + '=' + this.encodeValue(value[key]) + '^';
22267                 }
22268             }
22269             enc = 'o:' + flat.substring(0, flat.length-1);
22270         } else {
22271             enc = 's:' + value;
22272         }
22273         return escape(enc);
22274     }
22275 });
22276 /**
22277  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
22278  * Ext.container.Container on the document with a similar syntax to a CSS selector.
22279  *
22280  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
22281  *
22282  * - `component` or `.component`
22283  * - `gridpanel` or `.gridpanel`
22284  *
22285  * An itemId or id must be prefixed with a #
22286  *
22287  * - `#myContainer`
22288  *
22289  * Attributes must be wrapped in brackets
22290  *
22291  * - `component[autoScroll]`
22292  * - `panel[title="Test"]`
22293  *
22294  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
22295  * the candidate Component will be included in the query:
22296  *
22297  *     var disabledFields = myFormPanel.query("{isDisabled()}");
22298  *
22299  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
22300  *
22301  *     // Function receives array and returns a filtered array.
22302  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
22303  *         var i = 0, l = items.length, c, result = [];
22304  *         for (; i < l; i++) {
22305  *             if (!(c = items[i]).isValid()) {
22306  *                 result.push(c);
22307  *             }
22308  *         }
22309  *         return result;
22310  *     };
22311  *      
22312  *     var invalidFields = myFormPanel.query('field:invalid');
22313  *     if (invalidFields.length) {
22314  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
22315  *         for (var i = 0, l = invalidFields.length; i < l; i++) {
22316  *             invalidFields[i].getEl().frame("red");
22317  *         }
22318  *     }
22319  *
22320  * Default pseudos include:
22321  *
22322  * - not
22323  * - last
22324  *
22325  * Queries return an array of components.
22326  * Here are some example queries.
22327  *
22328  *     // retrieve all Ext.Panels in the document by xtype
22329  *     var panelsArray = Ext.ComponentQuery.query('panel');
22330  *
22331  *     // retrieve all Ext.Panels within the container with an id myCt
22332  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
22333  *
22334  *     // retrieve all direct children which are Ext.Panels within myCt
22335  *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
22336  *
22337  *     // retrieve all grids and trees
22338  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
22339  *
22340  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
22341  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
22342  * {@link Ext.Component#up}.
22343  */
22344 Ext.define('Ext.ComponentQuery', {
22345     singleton: true,
22346     uses: ['Ext.ComponentManager']
22347 }, function() {
22348
22349     var cq = this,
22350
22351         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
22352         // as a member on each item in the passed array.
22353         filterFnPattern = [
22354             'var r = [],',
22355                 'i = 0,',
22356                 'it = items,',
22357                 'l = it.length,',
22358                 'c;',
22359             'for (; i < l; i++) {',
22360                 'c = it[i];',
22361                 'if (c.{0}) {',
22362                    'r.push(c);',
22363                 '}',
22364             '}',
22365             'return r;'
22366         ].join(''),
22367
22368         filterItems = function(items, operation) {
22369             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
22370             // The operation's method loops over each item in the candidate array and
22371             // returns an array of items which match its criteria
22372             return operation.method.apply(this, [ items ].concat(operation.args));
22373         },
22374
22375         getItems = function(items, mode) {
22376             var result = [],
22377                 i = 0,
22378                 length = items.length,
22379                 candidate,
22380                 deep = mode !== '>';
22381                 
22382             for (; i < length; i++) {
22383                 candidate = items[i];
22384                 if (candidate.getRefItems) {
22385                     result = result.concat(candidate.getRefItems(deep));
22386                 }
22387             }
22388             return result;
22389         },
22390
22391         getAncestors = function(items) {
22392             var result = [],
22393                 i = 0,
22394                 length = items.length,
22395                 candidate;
22396             for (; i < length; i++) {
22397                 candidate = items[i];
22398                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
22399                     result.push(candidate);
22400                 }
22401             }
22402             return result;
22403         },
22404
22405         // Filters the passed candidate array and returns only items which match the passed xtype
22406         filterByXType = function(items, xtype, shallow) {
22407             if (xtype === '*') {
22408                 return items.slice();
22409             }
22410             else {
22411                 var result = [],
22412                     i = 0,
22413                     length = items.length,
22414                     candidate;
22415                 for (; i < length; i++) {
22416                     candidate = items[i];
22417                     if (candidate.isXType(xtype, shallow)) {
22418                         result.push(candidate);
22419                     }
22420                 }
22421                 return result;
22422             }
22423         },
22424
22425         // Filters the passed candidate array and returns only items which have the passed className
22426         filterByClassName = function(items, className) {
22427             var EA = Ext.Array,
22428                 result = [],
22429                 i = 0,
22430                 length = items.length,
22431                 candidate;
22432             for (; i < length; i++) {
22433                 candidate = items[i];
22434                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
22435                     result.push(candidate);
22436                 }
22437             }
22438             return result;
22439         },
22440
22441         // Filters the passed candidate array and returns only items which have the specified property match
22442         filterByAttribute = function(items, property, operator, value) {
22443             var result = [],
22444                 i = 0,
22445                 length = items.length,
22446                 candidate;
22447             for (; i < length; i++) {
22448                 candidate = items[i];
22449                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
22450                     result.push(candidate);
22451                 }
22452             }
22453             return result;
22454         },
22455
22456         // Filters the passed candidate array and returns only items which have the specified itemId or id
22457         filterById = function(items, id) {
22458             var result = [],
22459                 i = 0,
22460                 length = items.length,
22461                 candidate;
22462             for (; i < length; i++) {
22463                 candidate = items[i];
22464                 if (candidate.getItemId() === id) {
22465                     result.push(candidate);
22466                 }
22467             }
22468             return result;
22469         },
22470
22471         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
22472         filterByPseudo = function(items, name, value) {
22473             return cq.pseudos[name](items, value);
22474         },
22475
22476         // Determines leading mode
22477         // > for direct child, and ^ to switch to ownerCt axis
22478         modeRe = /^(\s?([>\^])\s?|\s|$)/,
22479
22480         // Matches a token with possibly (true|false) appended for the "shallow" parameter
22481         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
22482
22483         matchers = [{
22484             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
22485             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
22486             method: filterByXType
22487         },{
22488             // checks for [attribute=value]
22489             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
22490             method: filterByAttribute
22491         }, {
22492             // checks for #cmpItemId
22493             re: /^#([\w\-]+)/,
22494             method: filterById
22495         }, {
22496             // checks for :<pseudo_class>(<selector>)
22497             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
22498             method: filterByPseudo
22499         }, {
22500             // checks for {<member_expression>}
22501             re: /^(?:\{([^\}]+)\})/,
22502             method: filterFnPattern
22503         }];
22504
22505     // @class Ext.ComponentQuery.Query
22506     // This internal class is completely hidden in documentation.
22507     cq.Query = Ext.extend(Object, {
22508         constructor: function(cfg) {
22509             cfg = cfg || {};
22510             Ext.apply(this, cfg);
22511         },
22512
22513         // Executes this Query upon the selected root.
22514         // The root provides the initial source of candidate Component matches which are progressively
22515         // filtered by iterating through this Query's operations cache.
22516         // If no root is provided, all registered Components are searched via the ComponentManager.
22517         // root may be a Container who's descendant Components are filtered
22518         // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
22519         // docked items within a Panel.
22520         // root may be an array of candidate Components to filter using this Query.
22521         execute : function(root) {
22522             var operations = this.operations,
22523                 i = 0,
22524                 length = operations.length,
22525                 operation,
22526                 workingItems;
22527
22528             // no root, use all Components in the document
22529             if (!root) {
22530                 workingItems = Ext.ComponentManager.all.getArray();
22531             }
22532             // Root is a candidate Array
22533             else if (Ext.isArray(root)) {
22534                 workingItems = root;
22535             }
22536
22537             // We are going to loop over our operations and take care of them
22538             // one by one.
22539             for (; i < length; i++) {
22540                 operation = operations[i];
22541
22542                 // The mode operation requires some custom handling.
22543                 // All other operations essentially filter down our current
22544                 // working items, while mode replaces our current working
22545                 // items by getting children from each one of our current
22546                 // working items. The type of mode determines the type of
22547                 // children we get. (e.g. > only gets direct children)
22548                 if (operation.mode === '^') {
22549                     workingItems = getAncestors(workingItems || [root]);
22550                 }
22551                 else if (operation.mode) {
22552                     workingItems = getItems(workingItems || [root], operation.mode);
22553                 }
22554                 else {
22555                     workingItems = filterItems(workingItems || getItems([root]), operation);
22556                 }
22557
22558                 // If this is the last operation, it means our current working
22559                 // items are the final matched items. Thus return them!
22560                 if (i === length -1) {
22561                     return workingItems;
22562                 }
22563             }
22564             return [];
22565         },
22566
22567         is: function(component) {
22568             var operations = this.operations,
22569                 components = Ext.isArray(component) ? component : [component],
22570                 originalLength = components.length,
22571                 lastOperation = operations[operations.length-1],
22572                 ln, i;
22573
22574             components = filterItems(components, lastOperation);
22575             if (components.length === originalLength) {
22576                 if (operations.length > 1) {
22577                     for (i = 0, ln = components.length; i < ln; i++) {
22578                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
22579                             return false;
22580                         }
22581                     }
22582                 }
22583                 return true;
22584             }
22585             return false;
22586         }
22587     });
22588
22589     Ext.apply(this, {
22590
22591         // private cache of selectors and matching ComponentQuery.Query objects
22592         cache: {},
22593
22594         // private cache of pseudo class filter functions
22595         pseudos: {
22596             not: function(components, selector){
22597                 var CQ = Ext.ComponentQuery,
22598                     i = 0,
22599                     length = components.length,
22600                     results = [],
22601                     index = -1,
22602                     component;
22603                 
22604                 for(; i < length; ++i) {
22605                     component = components[i];
22606                     if (!CQ.is(component, selector)) {
22607                         results[++index] = component;
22608                     }
22609                 }
22610                 return results;
22611             },
22612             last: function(components) {
22613                 return components[components.length - 1];
22614             }
22615         },
22616
22617         /**
22618          * Returns an array of matched Components from within the passed root object.
22619          *
22620          * This method filters returned Components in a similar way to how CSS selector based DOM
22621          * queries work using a textual selector string.
22622          *
22623          * See class summary for details.
22624          *
22625          * @param {String} selector The selector string to filter returned Components
22626          * @param {Ext.container.Container} root The Container within which to perform the query.
22627          * If omitted, all Components within the document are included in the search.
22628          * 
22629          * This parameter may also be an array of Components to filter according to the selector.</p>
22630          * @returns {Ext.Component[]} The matched Components.
22631          * 
22632          * @member Ext.ComponentQuery
22633          */
22634         query: function(selector, root) {
22635             var selectors = selector.split(','),
22636                 length = selectors.length,
22637                 i = 0,
22638                 results = [],
22639                 noDupResults = [], 
22640                 dupMatcher = {}, 
22641                 query, resultsLn, cmp;
22642
22643             for (; i < length; i++) {
22644                 selector = Ext.String.trim(selectors[i]);
22645                 query = this.cache[selector];
22646                 if (!query) {
22647                     this.cache[selector] = query = this.parse(selector);
22648                 }
22649                 results = results.concat(query.execute(root));
22650             }
22651
22652             // multiple selectors, potential to find duplicates
22653             // lets filter them out.
22654             if (length > 1) {
22655                 resultsLn = results.length;
22656                 for (i = 0; i < resultsLn; i++) {
22657                     cmp = results[i];
22658                     if (!dupMatcher[cmp.id]) {
22659                         noDupResults.push(cmp);
22660                         dupMatcher[cmp.id] = true;
22661                     }
22662                 }
22663                 results = noDupResults;
22664             }
22665             return results;
22666         },
22667
22668         /**
22669          * Tests whether the passed Component matches the selector string.
22670          * @param {Ext.Component} component The Component to test
22671          * @param {String} selector The selector string to test against.
22672          * @return {Boolean} True if the Component matches the selector.
22673          * @member Ext.ComponentQuery
22674          */
22675         is: function(component, selector) {
22676             if (!selector) {
22677                 return true;
22678             }
22679             var query = this.cache[selector];
22680             if (!query) {
22681                 this.cache[selector] = query = this.parse(selector);
22682             }
22683             return query.is(component);
22684         },
22685
22686         parse: function(selector) {
22687             var operations = [],
22688                 length = matchers.length,
22689                 lastSelector,
22690                 tokenMatch,
22691                 matchedChar,
22692                 modeMatch,
22693                 selectorMatch,
22694                 i, matcher, method;
22695
22696             // We are going to parse the beginning of the selector over and
22697             // over again, slicing off the selector any portions we converted into an
22698             // operation, until it is an empty string.
22699             while (selector && lastSelector !== selector) {
22700                 lastSelector = selector;
22701
22702                 // First we check if we are dealing with a token like #, * or an xtype
22703                 tokenMatch = selector.match(tokenRe);
22704
22705                 if (tokenMatch) {
22706                     matchedChar = tokenMatch[1];
22707
22708                     // If the token is prefixed with a # we push a filterById operation to our stack
22709                     if (matchedChar === '#') {
22710                         operations.push({
22711                             method: filterById,
22712                             args: [Ext.String.trim(tokenMatch[2])]
22713                         });
22714                     }
22715                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
22716                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
22717                     else if (matchedChar === '.') {
22718                         operations.push({
22719                             method: filterByClassName,
22720                             args: [Ext.String.trim(tokenMatch[2])]
22721                         });
22722                     }
22723                     // If the token is a * or an xtype string, we push a filterByXType
22724                     // operation to the stack.
22725                     else {
22726                         operations.push({
22727                             method: filterByXType,
22728                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
22729                         });
22730                     }
22731
22732                     // Now we slice of the part we just converted into an operation
22733                     selector = selector.replace(tokenMatch[0], '');
22734                 }
22735
22736                 // If the next part of the query is not a space or > or ^, it means we
22737                 // are going to check for more things that our current selection
22738                 // has to comply to.
22739                 while (!(modeMatch = selector.match(modeRe))) {
22740                     // Lets loop over each type of matcher and execute it
22741                     // on our current selector.
22742                     for (i = 0; selector && i < length; i++) {
22743                         matcher = matchers[i];
22744                         selectorMatch = selector.match(matcher.re);
22745                         method = matcher.method;
22746
22747                         // If we have a match, add an operation with the method
22748                         // associated with this matcher, and pass the regular
22749                         // expression matches are arguments to the operation.
22750                         if (selectorMatch) {
22751                             operations.push({
22752                                 method: Ext.isString(matcher.method)
22753                                     // Turn a string method into a function by formatting the string with our selector matche expression
22754                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
22755                                     // Every expression may be different in different selectors.
22756                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
22757                                     : matcher.method,
22758                                 args: selectorMatch.slice(1)
22759                             });
22760                             selector = selector.replace(selectorMatch[0], '');
22761                             break; // Break on match
22762                         }
22763                     }
22764                 }
22765
22766                 // Now we are going to check for a mode change. This means a space
22767                 // or a > to determine if we are going to select all the children
22768                 // of the currently matched items, or a ^ if we are going to use the
22769                 // ownerCt axis as the candidate source.
22770                 if (modeMatch[1]) { // Assignment, and test for truthiness!
22771                     operations.push({
22772                         mode: modeMatch[2]||modeMatch[1]
22773                     });
22774                     selector = selector.replace(modeMatch[0], '');
22775                 }
22776             }
22777
22778             //  Now that we have all our operations in an array, we are going
22779             // to create a new Query using these operations.
22780             return new cq.Query({
22781                 operations: operations
22782             });
22783         }
22784     });
22785 });
22786 /**
22787  * @class Ext.util.HashMap
22788  * <p>
22789  * Represents a collection of a set of key and value pairs. Each key in the HashMap
22790  * must be unique, the same key cannot exist twice. Access to items is provided via
22791  * the key only. Sample usage:
22792  * <pre><code>
22793 var map = new Ext.util.HashMap();
22794 map.add('key1', 1);
22795 map.add('key2', 2);
22796 map.add('key3', 3);
22797
22798 map.each(function(key, value, length){
22799     console.log(key, value, length);
22800 });
22801  * </code></pre>
22802  * </p>
22803  *
22804  * <p>The HashMap is an unordered class,
22805  * there is no guarantee when iterating over the items that they will be in any particular
22806  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
22807  * </p>
22808  */
22809 Ext.define('Ext.util.HashMap', {
22810     mixins: {
22811         observable: 'Ext.util.Observable'
22812     },
22813
22814     /**
22815      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
22816      * A default is provided that returns the <b>id</b> property on the object. This function is only used
22817      * if the add method is called with a single argument.
22818      */
22819
22820     /**
22821      * Creates new HashMap.
22822      * @param {Object} config (optional) Config object.
22823      */
22824     constructor: function(config) {
22825         config = config || {};
22826         
22827         var me = this,
22828             keyFn = config.keyFn;
22829
22830         me.addEvents(
22831             /**
22832              * @event add
22833              * Fires when a new item is added to the hash
22834              * @param {Ext.util.HashMap} this.
22835              * @param {String} key The key of the added item.
22836              * @param {Object} value The value of the added item.
22837              */
22838             'add',
22839             /**
22840              * @event clear
22841              * Fires when the hash is cleared.
22842              * @param {Ext.util.HashMap} this.
22843              */
22844             'clear',
22845             /**
22846              * @event remove
22847              * Fires when an item is removed from the hash.
22848              * @param {Ext.util.HashMap} this.
22849              * @param {String} key The key of the removed item.
22850              * @param {Object} value The value of the removed item.
22851              */
22852             'remove',
22853             /**
22854              * @event replace
22855              * Fires when an item is replaced in the hash.
22856              * @param {Ext.util.HashMap} this.
22857              * @param {String} key The key of the replaced item.
22858              * @param {Object} value The new value for the item.
22859              * @param {Object} old The old value for the item.
22860              */
22861             'replace'
22862         );
22863
22864         me.mixins.observable.constructor.call(me, config);
22865         me.clear(true);
22866         
22867         if (keyFn) {
22868             me.getKey = keyFn;
22869         }
22870     },
22871
22872     /**
22873      * Gets the number of items in the hash.
22874      * @return {Number} The number of items in the hash.
22875      */
22876     getCount: function() {
22877         return this.length;
22878     },
22879
22880     /**
22881      * Implementation for being able to extract the key from an object if only
22882      * a single argument is passed.
22883      * @private
22884      * @param {String} key The key
22885      * @param {Object} value The value
22886      * @return {Array} [key, value]
22887      */
22888     getData: function(key, value) {
22889         // if we have no value, it means we need to get the key from the object
22890         if (value === undefined) {
22891             value = key;
22892             key = this.getKey(value);
22893         }
22894
22895         return [key, value];
22896     },
22897
22898     /**
22899      * Extracts the key from an object. This is a default implementation, it may be overridden
22900      * @param {Object} o The object to get the key from
22901      * @return {String} The key to use.
22902      */
22903     getKey: function(o) {
22904         return o.id;
22905     },
22906
22907     /**
22908      * Adds an item to the collection. Fires the {@link #add} event when complete.
22909      * @param {String} key <p>The key to associate with the item, or the new item.</p>
22910      * <p>If a {@link #getKey} implementation was specified for this HashMap,
22911      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
22912      * the HashMap will be able to <i>derive</i> the key for the new item.
22913      * In this case just pass the new item in this parameter.</p>
22914      * @param {Object} o The item to add.
22915      * @return {Object} The item added.
22916      */
22917     add: function(key, value) {
22918         var me = this,
22919             data;
22920
22921         if (arguments.length === 1) {
22922             value = key;
22923             key = me.getKey(value);
22924         }
22925
22926         if (me.containsKey(key)) {
22927             return me.replace(key, value);
22928         }
22929
22930         data = me.getData(key, value);
22931         key = data[0];
22932         value = data[1];
22933         me.map[key] = value;
22934         ++me.length;
22935         me.fireEvent('add', me, key, value);
22936         return value;
22937     },
22938
22939     /**
22940      * Replaces an item in the hash. If the key doesn't exist, the
22941      * {@link #add} method will be used.
22942      * @param {String} key The key of the item.
22943      * @param {Object} value The new value for the item.
22944      * @return {Object} The new value of the item.
22945      */
22946     replace: function(key, value) {
22947         var me = this,
22948             map = me.map,
22949             old;
22950
22951         if (!me.containsKey(key)) {
22952             me.add(key, value);
22953         }
22954         old = map[key];
22955         map[key] = value;
22956         me.fireEvent('replace', me, key, value, old);
22957         return value;
22958     },
22959
22960     /**
22961      * Remove an item from the hash.
22962      * @param {Object} o The value of the item to remove.
22963      * @return {Boolean} True if the item was successfully removed.
22964      */
22965     remove: function(o) {
22966         var key = this.findKey(o);
22967         if (key !== undefined) {
22968             return this.removeAtKey(key);
22969         }
22970         return false;
22971     },
22972
22973     /**
22974      * Remove an item from the hash.
22975      * @param {String} key The key to remove.
22976      * @return {Boolean} True if the item was successfully removed.
22977      */
22978     removeAtKey: function(key) {
22979         var me = this,
22980             value;
22981
22982         if (me.containsKey(key)) {
22983             value = me.map[key];
22984             delete me.map[key];
22985             --me.length;
22986             me.fireEvent('remove', me, key, value);
22987             return true;
22988         }
22989         return false;
22990     },
22991
22992     /**
22993      * Retrieves an item with a particular key.
22994      * @param {String} key The key to lookup.
22995      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
22996      */
22997     get: function(key) {
22998         return this.map[key];
22999     },
23000
23001     /**
23002      * Removes all items from the hash.
23003      * @return {Ext.util.HashMap} this
23004      */
23005     clear: function(/* private */ initial) {
23006         var me = this;
23007         me.map = {};
23008         me.length = 0;
23009         if (initial !== true) {
23010             me.fireEvent('clear', me);
23011         }
23012         return me;
23013     },
23014
23015     /**
23016      * Checks whether a key exists in the hash.
23017      * @param {String} key The key to check for.
23018      * @return {Boolean} True if they key exists in the hash.
23019      */
23020     containsKey: function(key) {
23021         return this.map[key] !== undefined;
23022     },
23023
23024     /**
23025      * Checks whether a value exists in the hash.
23026      * @param {Object} value The value to check for.
23027      * @return {Boolean} True if the value exists in the dictionary.
23028      */
23029     contains: function(value) {
23030         return this.containsKey(this.findKey(value));
23031     },
23032
23033     /**
23034      * Return all of the keys in the hash.
23035      * @return {Array} An array of keys.
23036      */
23037     getKeys: function() {
23038         return this.getArray(true);
23039     },
23040
23041     /**
23042      * Return all of the values in the hash.
23043      * @return {Array} An array of values.
23044      */
23045     getValues: function() {
23046         return this.getArray(false);
23047     },
23048
23049     /**
23050      * Gets either the keys/values in an array from the hash.
23051      * @private
23052      * @param {Boolean} isKey True to extract the keys, otherwise, the value
23053      * @return {Array} An array of either keys/values from the hash.
23054      */
23055     getArray: function(isKey) {
23056         var arr = [],
23057             key,
23058             map = this.map;
23059         for (key in map) {
23060             if (map.hasOwnProperty(key)) {
23061                 arr.push(isKey ? key: map[key]);
23062             }
23063         }
23064         return arr;
23065     },
23066
23067     /**
23068      * Executes the specified function once for each item in the hash.
23069      * Returning false from the function will cease iteration.
23070      *
23071      * The paramaters passed to the function are:
23072      * <div class="mdetail-params"><ul>
23073      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
23074      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
23075      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
23076      * </ul></div>
23077      * @param {Function} fn The function to execute.
23078      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
23079      * @return {Ext.util.HashMap} this
23080      */
23081     each: function(fn, scope) {
23082         // copy items so they may be removed during iteration.
23083         var items = Ext.apply({}, this.map),
23084             key,
23085             length = this.length;
23086
23087         scope = scope || this;
23088         for (key in items) {
23089             if (items.hasOwnProperty(key)) {
23090                 if (fn.call(scope, key, items[key], length) === false) {
23091                     break;
23092                 }
23093             }
23094         }
23095         return this;
23096     },
23097
23098     /**
23099      * Performs a shallow copy on this hash.
23100      * @return {Ext.util.HashMap} The new hash object.
23101      */
23102     clone: function() {
23103         var hash = new this.self(),
23104             map = this.map,
23105             key;
23106
23107         hash.suspendEvents();
23108         for (key in map) {
23109             if (map.hasOwnProperty(key)) {
23110                 hash.add(key, map[key]);
23111             }
23112         }
23113         hash.resumeEvents();
23114         return hash;
23115     },
23116
23117     /**
23118      * @private
23119      * Find the key for a value.
23120      * @param {Object} value The value to find.
23121      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
23122      */
23123     findKey: function(value) {
23124         var key,
23125             map = this.map;
23126
23127         for (key in map) {
23128             if (map.hasOwnProperty(key) && map[key] === value) {
23129                 return key;
23130             }
23131         }
23132         return undefined;
23133     }
23134 });
23135
23136 /**
23137  * @class Ext.state.Manager
23138  * This is the global state manager. By default all components that are "state aware" check this class
23139  * for state information if you don't pass them a custom state provider. In order for this class
23140  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23141  <pre><code>
23142 // in your initialization function
23143 init : function(){
23144    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23145    var win = new Window(...);
23146    win.restoreState();
23147 }
23148  </code></pre>
23149  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23150  * there is a common interface that can be used without needing to refer to a specific provider instance
23151  * in every component.
23152  * @singleton
23153  * @docauthor Evan Trimboli <evan@sencha.com>
23154  */
23155 Ext.define('Ext.state.Manager', {
23156     singleton: true,
23157     requires: ['Ext.state.Provider'],
23158     constructor: function() {
23159         this.provider = Ext.create('Ext.state.Provider');
23160     },
23161     
23162     
23163     /**
23164      * Configures the default state provider for your application
23165      * @param {Ext.state.Provider} stateProvider The state provider to set
23166      */
23167     setProvider : function(stateProvider){
23168         this.provider = stateProvider;
23169     },
23170
23171     /**
23172      * Returns the current value for a key
23173      * @param {String} name The key name
23174      * @param {Object} defaultValue The default value to return if the key lookup does not match
23175      * @return {Object} The state data
23176      */
23177     get : function(key, defaultValue){
23178         return this.provider.get(key, defaultValue);
23179     },
23180
23181     /**
23182      * Sets the value for a key
23183      * @param {String} name The key name
23184      * @param {Object} value The state data
23185      */
23186      set : function(key, value){
23187         this.provider.set(key, value);
23188     },
23189
23190     /**
23191      * Clears a value from the state
23192      * @param {String} name The key name
23193      */
23194     clear : function(key){
23195         this.provider.clear(key);
23196     },
23197
23198     /**
23199      * Gets the currently configured state provider
23200      * @return {Ext.state.Provider} The state provider
23201      */
23202     getProvider : function(){
23203         return this.provider;
23204     }
23205 });
23206 /**
23207  * @class Ext.state.Stateful
23208  * A mixin for being able to save the state of an object to an underlying
23209  * {@link Ext.state.Provider}.
23210  */
23211 Ext.define('Ext.state.Stateful', {
23212
23213     /* Begin Definitions */
23214
23215    mixins: {
23216         observable: 'Ext.util.Observable'
23217     },
23218
23219     requires: ['Ext.state.Manager'],
23220
23221     /* End Definitions */
23222
23223     /**
23224      * @cfg {Boolean} stateful
23225      * <p>A flag which causes the object to attempt to restore the state of
23226      * internal properties from a saved state on startup. The object must have
23227      * a <code>{@link #stateId}</code> for state to be managed.
23228      * Auto-generated ids are not guaranteed to be stable across page loads and
23229      * cannot be relied upon to save and restore the same state for a object.<p>
23230      * <p>For state saving to work, the state manager's provider must have been
23231      * set to an implementation of {@link Ext.state.Provider} which overrides the
23232      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23233      * methods to save and recall name/value pairs. A built-in implementation,
23234      * {@link Ext.state.CookieProvider} is available.</p>
23235      * <p>To set the state provider for the current page:</p>
23236      * <pre><code>
23237 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23238     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23239 }));
23240      * </code></pre>
23241      * <p>A stateful object attempts to save state when one of the events
23242      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23243      * <p>To save state, a stateful object first serializes its state by
23244      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23245      * nothing. The developer must provide an implementation which returns an
23246      * object hash which represents the restorable state of the object.</p>
23247      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23248      * which uses the configured {@link Ext.state.Provider} to save the object
23249      * keyed by the <code>{@link #stateId}</code>.</p>
23250      * <p>During construction, a stateful object attempts to <i>restore</i>
23251      * its state by calling {@link Ext.state.Manager#get} passing the
23252      * <code>{@link #stateId}</code></p>
23253      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23254      * The default implementation of <code>{@link #applyState}</code> simply copies
23255      * properties into the object, but a developer may override this to support
23256      * more behaviour.</p>
23257      * <p>You can perform extra processing on state save and restore by attaching
23258      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23259      * {@link #beforestatesave} and {@link #statesave} events.</p>
23260      */
23261     stateful: true,
23262
23263     /**
23264      * @cfg {String} stateId
23265      * The unique id for this object to use for state management purposes.
23266      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23267      */
23268
23269     /**
23270      * @cfg {String[]} stateEvents
23271      * <p>An array of events that, when fired, should trigger this object to
23272      * save its state. Defaults to none. <code>stateEvents</code> may be any type
23273      * of event supported by this object, including browser or custom events
23274      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23275      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23276      * restoring object state.</p>
23277      */
23278
23279     /**
23280      * @cfg {Number} saveDelay
23281      * A buffer to be applied if many state events are fired within a short period.
23282      */
23283     saveDelay: 100,
23284
23285     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23286
23287     constructor: function(config) {
23288         var me = this;
23289
23290         config = config || {};
23291         if (Ext.isDefined(config.stateful)) {
23292             me.stateful = config.stateful;
23293         }
23294         if (Ext.isDefined(config.saveDelay)) {
23295             me.saveDelay = config.saveDelay;
23296         }
23297         me.stateId = me.stateId || config.stateId;
23298
23299         if (!me.stateEvents) {
23300             me.stateEvents = [];
23301         }
23302         if (config.stateEvents) {
23303             me.stateEvents.concat(config.stateEvents);
23304         }
23305         this.addEvents(
23306             /**
23307              * @event beforestaterestore
23308              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23309              * @param {Ext.state.Stateful} this
23310              * @param {Object} state The hash of state values returned from the StateProvider. If this
23311              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23312              * that simply copies property values into this object. The method maybe overriden to
23313              * provide custom state restoration.
23314              */
23315             'beforestaterestore',
23316
23317             /**
23318              * @event staterestore
23319              * Fires after the state of the object is restored.
23320              * @param {Ext.state.Stateful} this
23321              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23322              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23323              * object. The method maybe overriden to provide custom state restoration.
23324              */
23325             'staterestore',
23326
23327             /**
23328              * @event beforestatesave
23329              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23330              * @param {Ext.state.Stateful} this
23331              * @param {Object} state The hash of state values. This is determined by calling
23332              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23333              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23334              * has a null implementation.
23335              */
23336             'beforestatesave',
23337
23338             /**
23339              * @event statesave
23340              * Fires after the state of the object is saved to the configured state provider.
23341              * @param {Ext.state.Stateful} this
23342              * @param {Object} state The hash of state values. This is determined by calling
23343              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23344              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23345              * has a null implementation.
23346              */
23347             'statesave'
23348         );
23349         me.mixins.observable.constructor.call(me);
23350         if (me.stateful !== false) {
23351             me.initStateEvents();
23352             me.initState();
23353         }
23354     },
23355
23356     /**
23357      * Initializes any state events for this object.
23358      * @private
23359      */
23360     initStateEvents: function() {
23361         this.addStateEvents(this.stateEvents);
23362     },
23363
23364     /**
23365      * Add events that will trigger the state to be saved.
23366      * @param {String/String[]} events The event name or an array of event names.
23367      */
23368     addStateEvents: function(events){
23369         if (!Ext.isArray(events)) {
23370             events = [events];
23371         }
23372
23373         var me = this,
23374             i = 0,
23375             len = events.length;
23376
23377         for (; i < len; ++i) {
23378             me.on(events[i], me.onStateChange, me);
23379         }
23380     },
23381
23382     /**
23383      * This method is called when any of the {@link #stateEvents} are fired.
23384      * @private
23385      */
23386     onStateChange: function(){
23387         var me = this,
23388             delay = me.saveDelay;
23389
23390         if (delay > 0) {
23391             if (!me.stateTask) {
23392                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23393             }
23394             me.stateTask.delay(me.saveDelay);
23395         } else {
23396             me.saveState();
23397         }
23398     },
23399
23400     /**
23401      * Saves the state of the object to the persistence store.
23402      * @private
23403      */
23404     saveState: function() {
23405         var me = this,
23406             id,
23407             state;
23408
23409         if (me.stateful !== false) {
23410             id = me.getStateId();
23411             if (id) {
23412                 state = me.getState();
23413                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23414                     Ext.state.Manager.set(id, state);
23415                     me.fireEvent('statesave', me, state);
23416                 }
23417             }
23418         }
23419     },
23420
23421     /**
23422      * Gets the current state of the object. By default this function returns null,
23423      * it should be overridden in subclasses to implement methods for getting the state.
23424      * @return {Object} The current state
23425      */
23426     getState: function(){
23427         return null;
23428     },
23429
23430     /**
23431      * Applies the state to the object. This should be overridden in subclasses to do
23432      * more complex state operations. By default it applies the state properties onto
23433      * the current object.
23434      * @param {Object} state The state
23435      */
23436     applyState: function(state) {
23437         if (state) {
23438             Ext.apply(this, state);
23439         }
23440     },
23441
23442     /**
23443      * Gets the state id for this object.
23444      * @return {String} The state id, null if not found.
23445      */
23446     getStateId: function() {
23447         var me = this,
23448             id = me.stateId;
23449
23450         if (!id) {
23451             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
23452         }
23453         return id;
23454     },
23455
23456     /**
23457      * Initializes the state of the object upon construction.
23458      * @private
23459      */
23460     initState: function(){
23461         var me = this,
23462             id = me.getStateId(),
23463             state;
23464
23465         if (me.stateful !== false) {
23466             if (id) {
23467                 state = Ext.state.Manager.get(id);
23468                 if (state) {
23469                     state = Ext.apply({}, state);
23470                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
23471                         me.applyState(state);
23472                         me.fireEvent('staterestore', me, state);
23473                     }
23474                 }
23475             }
23476         }
23477     },
23478
23479     /**
23480      * Conditionally saves a single property from this object to the given state object.
23481      * The idea is to only save state which has changed from the initial state so that
23482      * current software settings do not override future software settings. Only those
23483      * values that are user-changed state should be saved.
23484      *
23485      * @param {String} propName The name of the property to save.
23486      * @param {Object} state The state object in to which to save the property.
23487      * @param {String} stateName (optional) The name to use for the property in state.
23488      * @return {Boolean} True if the property was saved, false if not.
23489      */
23490     savePropToState: function (propName, state, stateName) {
23491         var me = this,
23492             value = me[propName],
23493             config = me.initialConfig;
23494
23495         if (me.hasOwnProperty(propName)) {
23496             if (!config || config[propName] !== value) {
23497                 if (state) {
23498                     state[stateName || propName] = value;
23499                 }
23500                 return true;
23501             }
23502         }
23503         return false;
23504     },
23505
23506     savePropsToState: function (propNames, state) {
23507         var me = this;
23508         Ext.each(propNames, function (propName) {
23509             me.savePropToState(propName, state);
23510         });
23511         return state;
23512     },
23513
23514     /**
23515      * Destroys this stateful object.
23516      */
23517     destroy: function(){
23518         var task = this.stateTask;
23519         if (task) {
23520             task.cancel();
23521         }
23522         this.clearListeners();
23523
23524     }
23525
23526 });
23527
23528 /**
23529  * Base Manager class
23530  */
23531 Ext.define('Ext.AbstractManager', {
23532
23533     /* Begin Definitions */
23534
23535     requires: ['Ext.util.HashMap'],
23536
23537     /* End Definitions */
23538
23539     typeName: 'type',
23540
23541     constructor: function(config) {
23542         Ext.apply(this, config || {});
23543
23544         /**
23545          * @property {Ext.util.HashMap} all
23546          * Contains all of the items currently managed
23547          */
23548         this.all = Ext.create('Ext.util.HashMap');
23549
23550         this.types = {};
23551     },
23552
23553     /**
23554      * Returns an item by id.
23555      * For additional details see {@link Ext.util.HashMap#get}.
23556      * @param {String} id The id of the item
23557      * @return {Object} The item, undefined if not found.
23558      */
23559     get : function(id) {
23560         return this.all.get(id);
23561     },
23562
23563     /**
23564      * Registers an item to be managed
23565      * @param {Object} item The item to register
23566      */
23567     register: function(item) {
23568         this.all.add(item);
23569     },
23570
23571     /**
23572      * Unregisters an item by removing it from this manager
23573      * @param {Object} item The item to unregister
23574      */
23575     unregister: function(item) {
23576         this.all.remove(item);
23577     },
23578
23579     /**
23580      * Registers a new item constructor, keyed by a type key.
23581      * @param {String} type The mnemonic string by which the class may be looked up.
23582      * @param {Function} cls The new instance class.
23583      */
23584     registerType : function(type, cls) {
23585         this.types[type] = cls;
23586         cls[this.typeName] = type;
23587     },
23588
23589     /**
23590      * Checks if an item type is registered.
23591      * @param {String} type The mnemonic string by which the class may be looked up
23592      * @return {Boolean} Whether the type is registered.
23593      */
23594     isRegistered : function(type){
23595         return this.types[type] !== undefined;
23596     },
23597
23598     /**
23599      * Creates and returns an instance of whatever this manager manages, based on the supplied type and
23600      * config object.
23601      * @param {Object} config The config object
23602      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
23603      * @return {Object} The instance of whatever this manager is managing
23604      */
23605     create: function(config, defaultType) {
23606         var type        = config[this.typeName] || config.type || defaultType,
23607             Constructor = this.types[type];
23608
23609
23610         return new Constructor(config);
23611     },
23612
23613     /**
23614      * Registers a function that will be called when an item with the specified id is added to the manager.
23615      * This will happen on instantiation.
23616      * @param {String} id The item id
23617      * @param {Function} fn The callback function. Called with a single parameter, the item.
23618      * @param {Object} scope The scope (this reference) in which the callback is executed.
23619      * Defaults to the item.
23620      */
23621     onAvailable : function(id, fn, scope){
23622         var all = this.all,
23623             item;
23624         
23625         if (all.containsKey(id)) {
23626             item = all.get(id);
23627             fn.call(scope || item, item);
23628         } else {
23629             all.on('add', function(map, key, item){
23630                 if (key == id) {
23631                     fn.call(scope || item, item);
23632                     all.un('add', fn, scope);
23633                 }
23634             });
23635         }
23636     },
23637     
23638     /**
23639      * Executes the specified function once for each item in the collection.
23640      * @param {Function} fn The function to execute.
23641      * @param {String} fn.key The key of the item
23642      * @param {Number} fn.value The value of the item
23643      * @param {Number} fn.length The total number of items in the collection
23644      * @param {Boolean} fn.return False to cease iteration.
23645      * @param {Object} scope The scope to execute in. Defaults to `this`.
23646      */
23647     each: function(fn, scope){
23648         this.all.each(fn, scope || this);    
23649     },
23650     
23651     /**
23652      * Gets the number of items in the collection.
23653      * @return {Number} The number of items in the collection.
23654      */
23655     getCount: function(){
23656         return this.all.getCount();
23657     }
23658 });
23659
23660 /**
23661  * @class Ext.ComponentManager
23662  * @extends Ext.AbstractManager
23663  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
23664  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
23665  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
23666  * <p>This object also provides a registry of available Component <i>classes</i>
23667  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
23668  * The <code>xtype</code> provides a way to avoid instantiating child Components
23669  * when creating a full, nested config object for a complete Ext page.</p>
23670  * <p>A child Component may be specified simply as a <i>config object</i>
23671  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
23672  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
23673  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
23674  * @singleton
23675  */
23676 Ext.define('Ext.ComponentManager', {
23677     extend: 'Ext.AbstractManager',
23678     alternateClassName: 'Ext.ComponentMgr',
23679     
23680     singleton: true,
23681     
23682     typeName: 'xtype',
23683     
23684     /**
23685      * Creates a new Component from the specified config object using the
23686      * config object's xtype to determine the class to instantiate.
23687      * @param {Object} config A configuration object for the Component you wish to create.
23688      * @param {Function} defaultType (optional) The constructor to provide the default Component type if
23689      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
23690      * @return {Ext.Component} The newly instantiated Component.
23691      */
23692     create: function(component, defaultType){
23693         if (component instanceof Ext.AbstractComponent) {
23694             return component;
23695         }
23696         else if (Ext.isString(component)) {
23697             return Ext.createByAlias('widget.' + component);
23698         }
23699         else {
23700             var type = component.xtype || defaultType,
23701                 config = component;
23702             
23703             return Ext.createByAlias('widget.' + type, config);
23704         }
23705     },
23706
23707     registerType: function(type, cls) {
23708         this.types[type] = cls;
23709         cls[this.typeName] = type;
23710         cls.prototype[this.typeName] = type;
23711     }
23712 });
23713 /**
23714  * An abstract base class which provides shared methods for Components across the Sencha product line.
23715  *
23716  * Please refer to sub class's documentation
23717  * @private
23718  */
23719 Ext.define('Ext.AbstractComponent', {
23720
23721     /* Begin Definitions */
23722     requires: [
23723         'Ext.ComponentQuery',
23724         'Ext.ComponentManager'
23725     ],
23726
23727     mixins: {
23728         observable: 'Ext.util.Observable',
23729         animate: 'Ext.util.Animate',
23730         state: 'Ext.state.Stateful'
23731     },
23732
23733     // The "uses" property specifies class which are used in an instantiated AbstractComponent.
23734     // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
23735     uses: [
23736         'Ext.PluginManager',
23737         'Ext.ComponentManager',
23738         'Ext.Element',
23739         'Ext.DomHelper',
23740         'Ext.XTemplate',
23741         'Ext.ComponentQuery',
23742         'Ext.ComponentLoader',
23743         'Ext.EventManager',
23744         'Ext.layout.Layout',
23745         'Ext.layout.component.Auto',
23746         'Ext.LoadMask',
23747         'Ext.ZIndexManager'
23748     ],
23749
23750     statics: {
23751         AUTO_ID: 1000
23752     },
23753
23754     /* End Definitions */
23755
23756     isComponent: true,
23757
23758     getAutoId: function() {
23759         return ++Ext.AbstractComponent.AUTO_ID;
23760     },
23761
23762
23763     /**
23764      * @cfg {String} id
23765      * The **unique id of this component instance.**
23766      *
23767      * It should not be necessary to use this configuration except for singleton objects in your application. Components
23768      * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
23769      *
23770      * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
23771      * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
23772      * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
23773      * its descendant Components by selector.
23774      *
23775      * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
23776      * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
23777      * component uniquely, and also to select sub-elements using this component's id as the parent.
23778      *
23779      * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
23780      *
23781      * **Note**: to access the container of a Component see `{@link #ownerCt}`.
23782      *
23783      * Defaults to an {@link #getId auto-assigned id}.
23784      */
23785
23786     /**
23787      * @cfg {String} itemId
23788      * An itemId can be used as an alternative way to get a reference to a component when no object reference is
23789      * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
23790      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
23791      * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
23792      * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
23793      * which requires a **unique** `{@link #id}`.
23794      *
23795      *     var c = new Ext.panel.Panel({ //
23796      *         {@link Ext.Component#height height}: 300,
23797      *         {@link #renderTo}: document.body,
23798      *         {@link Ext.container.Container#layout layout}: 'auto',
23799      *         {@link Ext.container.Container#items items}: [
23800      *             {
23801      *                 itemId: 'p1',
23802      *                 {@link Ext.panel.Panel#title title}: 'Panel 1',
23803      *                 {@link Ext.Component#height height}: 150
23804      *             },
23805      *             {
23806      *                 itemId: 'p2',
23807      *                 {@link Ext.panel.Panel#title title}: 'Panel 2',
23808      *                 {@link Ext.Component#height height}: 150
23809      *             }
23810      *         ]
23811      *     })
23812      *     p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
23813      *     p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
23814      *
23815      * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
23816      * `{@link Ext.container.Container#child}`.
23817      *
23818      * **Note**: to access the container of an item see {@link #ownerCt}.
23819      */
23820
23821     /**
23822      * @property {Ext.Container} ownerCt
23823      * This Component's owner {@link Ext.container.Container Container} (is set automatically
23824      * when this Component is added to a Container). Read-only.
23825      *
23826      * **Note**: to access items within the Container see {@link #itemId}.
23827      */
23828
23829     /**
23830      * @property {Boolean} layoutManagedWidth
23831      * @private
23832      * Flag set by the container layout to which this Component is added.
23833      * If the layout manages this Component's width, it sets the value to 1.
23834      * If it does NOT manage the width, it sets it to 2.
23835      * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
23836      */
23837
23838     /**
23839      * @property {Boolean} layoutManagedHeight
23840      * @private
23841      * Flag set by the container layout to which this Component is added.
23842      * If the layout manages this Component's height, it sets the value to 1.
23843      * If it does NOT manage the height, it sets it to 2.
23844      * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
23845      */
23846
23847     /**
23848      * @cfg {String/Object} autoEl
23849      * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
23850      * encapsulate this Component.
23851      *
23852      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
23853      * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
23854      * complex DOM structure specified by their own {@link #renderTpl}s.
23855      *
23856      * This is intended to allow the developer to create application-specific utility Components encapsulated by
23857      * different DOM elements. Example usage:
23858      *
23859      *     {
23860      *         xtype: 'component',
23861      *         autoEl: {
23862      *             tag: 'img',
23863      *             src: 'http://www.example.com/example.jpg'
23864      *         }
23865      *     }, {
23866      *         xtype: 'component',
23867      *         autoEl: {
23868      *             tag: 'blockquote',
23869      *             html: 'autoEl is cool!'
23870      *         }
23871      *     }, {
23872      *         xtype: 'container',
23873      *         autoEl: 'ul',
23874      *         cls: 'ux-unordered-list',
23875      *         items: {
23876      *             xtype: 'component',
23877      *             autoEl: 'li',
23878      *             html: 'First list item'
23879      *         }
23880      *     }
23881      */
23882
23883     /**
23884      * @cfg {Ext.XTemplate/String/String[]} renderTpl
23885      * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
23886      * {@link #getEl Element}.
23887      *
23888      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
23889      * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
23890      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
23891      * classes which use a more complex DOM structure, provide their own template definitions.
23892      *
23893      * This is intended to allow the developer to create application-specific utility Components with customized
23894      * internal structure.
23895      *
23896      * Upon rendering, any created child elements may be automatically imported into object properties using the
23897      * {@link #renderSelectors} and {@link #childEls} options.
23898      */
23899     renderTpl: null,
23900
23901     /**
23902      * @cfg {Object} renderData
23903      *
23904      * The data used by {@link #renderTpl} in addition to the following property values of the component:
23905      *
23906      * - id
23907      * - ui
23908      * - uiCls
23909      * - baseCls
23910      * - componentCls
23911      * - frame
23912      *
23913      * See {@link #renderSelectors} and {@link #childEls} for usage examples.
23914      */
23915
23916     /**
23917      * @cfg {Object} renderSelectors
23918      * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
23919      * created by the render process.
23920      *
23921      * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
23922      * and the found Elements are added as properties to the Component using the `renderSelector` property name.
23923      *
23924      * For example, a Component which renderes a title and description into its element:
23925      *
23926      *     Ext.create('Ext.Component', {
23927      *         renderTo: Ext.getBody(),
23928      *         renderTpl: [
23929      *             '<h1 class="title">{title}</h1>',
23930      *             '<p>{desc}</p>'
23931      *         ],
23932      *         renderData: {
23933      *             title: "Error",
23934      *             desc: "Something went wrong"
23935      *         },
23936      *         renderSelectors: {
23937      *             titleEl: 'h1.title',
23938      *             descEl: 'p'
23939      *         },
23940      *         listeners: {
23941      *             afterrender: function(cmp){
23942      *                 // After rendering the component will have a titleEl and descEl properties
23943      *                 cmp.titleEl.setStyle({color: "red"});
23944      *             }
23945      *         }
23946      *     });
23947      *
23948      * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
23949      * Component after render), see {@link #childEls} and {@link #addChildEls}.
23950      */
23951
23952     /**
23953      * @cfg {Object[]} childEls
23954      * An array describing the child elements of the Component. Each member of the array
23955      * is an object with these properties:
23956      *
23957      * - `name` - The property name on the Component for the child element.
23958      * - `itemId` - The id to combine with the Component's id that is the id of the child element.
23959      * - `id` - The id of the child element.
23960      *
23961      * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
23962      *
23963      * For example, a Component which renders a title and body text:
23964      *
23965      *     Ext.create('Ext.Component', {
23966      *         renderTo: Ext.getBody(),
23967      *         renderTpl: [
23968      *             '<h1 id="{id}-title">{title}</h1>',
23969      *             '<p>{msg}</p>',
23970      *         ],
23971      *         renderData: {
23972      *             title: "Error",
23973      *             msg: "Something went wrong"
23974      *         },
23975      *         childEls: ["title"],
23976      *         listeners: {
23977      *             afterrender: function(cmp){
23978      *                 // After rendering the component will have a title property
23979      *                 cmp.title.setStyle({color: "red"});
23980      *             }
23981      *         }
23982      *     });
23983      *
23984      * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
23985      */
23986
23987     /**
23988      * @cfg {String/HTMLElement/Ext.Element} renderTo
23989      * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
23990      *
23991      * **Notes:**
23992      *
23993      * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
23994      * It is the responsibility of the {@link Ext.container.Container Container}'s
23995      * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
23996      *
23997      * When using this config, a call to render() is not required.
23998      *
23999      * See `{@link #render}` also.
24000      */
24001
24002     /**
24003      * @cfg {Boolean} frame
24004      * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
24005      * graphical rounded frame around the Component content.
24006      *
24007      * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
24008      * Explorer prior to version 9 which do not support rounded corners natively.
24009      *
24010      * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
24011      */
24012
24013     /**
24014      * @property {Object} frameSize
24015      * Read-only property indicating the width of any framing elements which were added within the encapsulating element
24016      * to provide graphical, rounded borders. See the {@link #frame} config.
24017      *
24018      * This is an object containing the frame width in pixels for all four sides of the Component containing the
24019      * following properties:
24020      *
24021      * @property {Number} frameSize.top The width of the top framing element in pixels.
24022      * @property {Number} frameSize.right The width of the right framing element in pixels.
24023      * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
24024      * @property {Number} frameSize.left The width of the left framing element in pixels.
24025      */
24026
24027     /**
24028      * @cfg {String/Object} componentLayout
24029      * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
24030      * manager which sizes a Component's internal structure in response to the Component being sized.
24031      *
24032      * Generally, developers will not use this configuration as all provided Components which need their internal
24033      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
24034      *
24035      * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
24036      * class which simply sizes the Component's encapsulating element to the height and width specified in the
24037      * {@link #setSize} method.
24038      */
24039
24040     /**
24041      * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
24042      * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
24043      * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
24044      */
24045
24046     /**
24047      * @cfg {Object} data
24048      * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
24049      */
24050
24051     /**
24052      * @cfg {String} xtype
24053      * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
24054      * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
24055      *
24056      * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
24057      * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
24058      *
24059      *     Ext.define('PressMeButton', {
24060      *         extend: 'Ext.button.Button',
24061      *         alias: 'widget.pressmebutton',
24062      *         text: 'Press Me'
24063      *     })
24064      *
24065      * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
24066      * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
24067      * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
24068      * until they are actually needed. In complex, nested layouts containing many Components, this can make a
24069      * noticeable improvement in performance.
24070      *
24071      *     // Explicit creation of contained Components:
24072      *     var panel = new Ext.Panel({
24073      *        ...
24074      *        items: [
24075      *           Ext.create('Ext.button.Button', {
24076      *              text: 'OK'
24077      *           })
24078      *        ]
24079      *     };
24080      *
24081      *     // Implicit creation using xtype:
24082      *     var panel = new Ext.Panel({
24083      *        ...
24084      *        items: [{
24085      *           xtype: 'button',
24086      *           text: 'OK'
24087      *        }]
24088      *     };
24089      *
24090      * In the first example, the button will always be created immediately during the panel's initialization. With
24091      * many added Components, this approach could potentially slow the rendering of the page. In the second example,
24092      * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
24093      * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
24094      * will never consume any resources whatsoever.
24095      */
24096
24097     /**
24098      * @cfg {String} tplWriteMode
24099      * The Ext.(X)Template method to use when updating the content area of the Component.
24100      * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
24101      */
24102     tplWriteMode: 'overwrite',
24103
24104     /**
24105      * @cfg {String} [baseCls='x-component']
24106      * The base CSS class to apply to this components's element. This will also be prepended to elements within this
24107      * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
24108      * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
24109      * componentCls to add specific styling for this component.
24110      */
24111     baseCls: Ext.baseCSSPrefix + 'component',
24112
24113     /**
24114      * @cfg {String} componentCls
24115      * CSS Class to be added to a components root level element to give distinction to it via styling.
24116      */
24117
24118     /**
24119      * @cfg {String} [cls='']
24120      * An optional extra CSS class that will be added to this component's Element. This can be useful
24121      * for adding customized styles to the component or any of its children using standard CSS rules.
24122      */
24123
24124     /**
24125      * @cfg {String} [overCls='']
24126      * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
24127      * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
24128      * component or any of its children using standard CSS rules.
24129      */
24130
24131     /**
24132      * @cfg {String} [disabledCls='x-item-disabled']
24133      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
24134      */
24135     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
24136
24137     /**
24138      * @cfg {String/String[]} ui
24139      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
24140      */
24141     ui: 'default',
24142
24143     /**
24144      * @cfg {String[]} uiCls
24145      * An array of of classNames which are currently applied to this component
24146      * @private
24147      */
24148     uiCls: [],
24149
24150     /**
24151      * @cfg {String} style
24152      * A custom style specification to be applied to this component's Element. Should be a valid argument to
24153      * {@link Ext.Element#applyStyles}.
24154      *
24155      *     new Ext.panel.Panel({
24156      *         title: 'Some Title',
24157      *         renderTo: Ext.getBody(),
24158      *         width: 400, height: 300,
24159      *         layout: 'form',
24160      *         items: [{
24161      *             xtype: 'textarea',
24162      *             style: {
24163      *                 width: '95%',
24164      *                 marginBottom: '10px'
24165      *             }
24166      *         },
24167      *         new Ext.button.Button({
24168      *             text: 'Send',
24169      *             minWidth: '100',
24170      *             style: {
24171      *                 marginBottom: '10px'
24172      *             }
24173      *         })
24174      *         ]
24175      *     });
24176      */
24177
24178     /**
24179      * @cfg {Number} width
24180      * The width of this component in pixels.
24181      */
24182
24183     /**
24184      * @cfg {Number} height
24185      * The height of this component in pixels.
24186      */
24187
24188     /**
24189      * @cfg {Number/String} border
24190      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
24191      * be a CSS style specification for each style, for example: '10 5 3 10'.
24192      */
24193
24194     /**
24195      * @cfg {Number/String} padding
24196      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
24197      * can be a CSS style specification for each style, for example: '10 5 3 10'.
24198      */
24199
24200     /**
24201      * @cfg {Number/String} margin
24202      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
24203      * be a CSS style specification for each style, for example: '10 5 3 10'.
24204      */
24205
24206     /**
24207      * @cfg {Boolean} hidden
24208      * True to hide the component.
24209      */
24210     hidden: false,
24211
24212     /**
24213      * @cfg {Boolean} disabled
24214      * True to disable the component.
24215      */
24216     disabled: false,
24217
24218     /**
24219      * @cfg {Boolean} [draggable=false]
24220      * Allows the component to be dragged.
24221      */
24222
24223     /**
24224      * @property {Boolean} draggable
24225      * Read-only property indicating whether or not the component can be dragged
24226      */
24227     draggable: false,
24228
24229     /**
24230      * @cfg {Boolean} floating
24231      * Create the Component as a floating and use absolute positioning.
24232      *
24233      * 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
24234      * by the global {@link Ext.WindowManager WindowManager}.
24235      *
24236      * 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
24237      * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
24238      *
24239      * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
24240      */
24241     floating: false,
24242
24243     /**
24244      * @cfg {String} hideMode
24245      * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
24246      *
24247      *   - `'display'` : The Component will be hidden using the `display: none` style.
24248      *   - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
24249      *   - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
24250      *     This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
24251      *     Component having zero dimensions.
24252      */
24253     hideMode: 'display',
24254
24255     /**
24256      * @cfg {String} contentEl
24257      * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
24258      *
24259      * This config option is used to take an existing HTML element and place it in the layout element of a new component
24260      * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
24261      *
24262      * **Notes:**
24263      *
24264      * The specified HTML element is appended to the layout element of the component _after any configured
24265      * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
24266      * the {@link #render} event is fired.
24267      *
24268      * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
24269      * scheme that the Component may use. It is just HTML. Layouts operate on child
24270      * **`{@link Ext.container.Container#items items}`**.
24271      *
24272      * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
24273      * is rendered to the panel.
24274      */
24275
24276     /**
24277      * @cfg {String/Object} [html='']
24278      * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
24279      * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
24280      * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
24281      * is appended.
24282      */
24283
24284     /**
24285      * @cfg {Boolean} styleHtmlContent
24286      * True to automatically style the html inside the content target of this component (body for panels).
24287      */
24288     styleHtmlContent: false,
24289
24290     /**
24291      * @cfg {String} [styleHtmlCls='x-html']
24292      * The class that is added to the content target when you set styleHtmlContent to true.
24293      */
24294     styleHtmlCls: Ext.baseCSSPrefix + 'html',
24295
24296     /**
24297      * @cfg {Number} minHeight
24298      * The minimum value in pixels which this Component will set its height to.
24299      *
24300      * **Warning:** This will override any size management applied by layout managers.
24301      */
24302     /**
24303      * @cfg {Number} minWidth
24304      * The minimum value in pixels which this Component will set its width to.
24305      *
24306      * **Warning:** This will override any size management applied by layout managers.
24307      */
24308     /**
24309      * @cfg {Number} maxHeight
24310      * The maximum value in pixels which this Component will set its height to.
24311      *
24312      * **Warning:** This will override any size management applied by layout managers.
24313      */
24314     /**
24315      * @cfg {Number} maxWidth
24316      * The maximum value in pixels which this Component will set its width to.
24317      *
24318      * **Warning:** This will override any size management applied by layout managers.
24319      */
24320
24321     /**
24322      * @cfg {Ext.ComponentLoader/Object} loader
24323      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
24324      */
24325
24326     /**
24327      * @cfg {Boolean} autoShow
24328      * True to automatically show the component upon creation. This config option may only be used for
24329      * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
24330      */
24331     autoShow: false,
24332
24333     /**
24334      * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
24335      * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
24336      * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
24337      * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
24338      *
24339      * Specify as `true` to have this Component render to the document body upon first show.
24340      *
24341      * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
24342      * show.
24343      *
24344      * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
24345      */
24346     autoRender: false,
24347
24348     needsLayout: false,
24349
24350     // @private
24351     allowDomMove: true,
24352
24353     /**
24354      * @cfg {Object/Object[]} plugins
24355      * An object or array of objects that will provide custom functionality for this component. The only requirement for
24356      * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
24357      * is created, if any plugins are available, the component will call the init method on each plugin, passing a
24358      * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
24359      * its functionality.
24360      */
24361
24362     /**
24363      * @property {Boolean} rendered
24364      * Read-only property indicating whether or not the component has been rendered.
24365      */
24366     rendered: false,
24367
24368     /**
24369      * @property {Number} componentLayoutCounter
24370      * @private
24371      * The number of component layout calls made on this object.
24372      */
24373     componentLayoutCounter: 0,
24374
24375     weight: 0,
24376
24377     trimRe: /^\s+|\s+$/g,
24378     spacesRe: /\s+/,
24379
24380
24381     /**
24382      * @property {Boolean} maskOnDisable
24383      * This is an internal flag that you use when creating custom components. By default this is set to true which means
24384      * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
24385      * override this property to false since they want to implement custom disable logic.
24386      */
24387     maskOnDisable: true,
24388
24389     /**
24390      * Creates new Component.
24391      * @param {Object} config  (optional) Config object.
24392      */
24393     constructor : function(config) {
24394         var me = this,
24395             i, len;
24396
24397         config = config || {};
24398         me.initialConfig = config;
24399         Ext.apply(me, config);
24400
24401         me.addEvents(
24402             /**
24403              * @event beforeactivate
24404              * Fires before a Component has been visually activated. Returning false from an event listener can prevent
24405              * the activate from occurring.
24406              * @param {Ext.Component} this
24407              */
24408             'beforeactivate',
24409             /**
24410              * @event activate
24411              * Fires after a Component has been visually activated.
24412              * @param {Ext.Component} this
24413              */
24414             'activate',
24415             /**
24416              * @event beforedeactivate
24417              * Fires before a Component has been visually deactivated. Returning false from an event listener can
24418              * prevent the deactivate from occurring.
24419              * @param {Ext.Component} this
24420              */
24421             'beforedeactivate',
24422             /**
24423              * @event deactivate
24424              * Fires after a Component has been visually deactivated.
24425              * @param {Ext.Component} this
24426              */
24427             'deactivate',
24428             /**
24429              * @event added
24430              * Fires after a Component had been added to a Container.
24431              * @param {Ext.Component} this
24432              * @param {Ext.container.Container} container Parent Container
24433              * @param {Number} pos position of Component
24434              */
24435             'added',
24436             /**
24437              * @event disable
24438              * Fires after the component is disabled.
24439              * @param {Ext.Component} this
24440              */
24441             'disable',
24442             /**
24443              * @event enable
24444              * Fires after the component is enabled.
24445              * @param {Ext.Component} this
24446              */
24447             'enable',
24448             /**
24449              * @event beforeshow
24450              * Fires before the component is shown when calling the {@link #show} method. Return false from an event
24451              * handler to stop the show.
24452              * @param {Ext.Component} this
24453              */
24454             'beforeshow',
24455             /**
24456              * @event show
24457              * Fires after the component is shown when calling the {@link #show} method.
24458              * @param {Ext.Component} this
24459              */
24460             'show',
24461             /**
24462              * @event beforehide
24463              * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
24464              * handler to stop the hide.
24465              * @param {Ext.Component} this
24466              */
24467             'beforehide',
24468             /**
24469              * @event hide
24470              * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
24471              * method.
24472              * @param {Ext.Component} this
24473              */
24474             'hide',
24475             /**
24476              * @event removed
24477              * Fires when a component is removed from an Ext.container.Container
24478              * @param {Ext.Component} this
24479              * @param {Ext.container.Container} ownerCt Container which holds the component
24480              */
24481             'removed',
24482             /**
24483              * @event beforerender
24484              * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
24485              * {@link #render}.
24486              * @param {Ext.Component} this
24487              */
24488             'beforerender',
24489             /**
24490              * @event render
24491              * Fires after the component markup is {@link #rendered}.
24492              * @param {Ext.Component} this
24493              */
24494             'render',
24495             /**
24496              * @event afterrender
24497              * Fires after the component rendering is finished.
24498              *
24499              * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
24500              * afterRender method defined for the Component.
24501              * @param {Ext.Component} this
24502              */
24503             'afterrender',
24504             /**
24505              * @event beforedestroy
24506              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
24507              * {@link #destroy}.
24508              * @param {Ext.Component} this
24509              */
24510             'beforedestroy',
24511             /**
24512              * @event destroy
24513              * Fires after the component is {@link #destroy}ed.
24514              * @param {Ext.Component} this
24515              */
24516             'destroy',
24517             /**
24518              * @event resize
24519              * Fires after the component is resized.
24520              * @param {Ext.Component} this
24521              * @param {Number} adjWidth The box-adjusted width that was set
24522              * @param {Number} adjHeight The box-adjusted height that was set
24523              */
24524             'resize',
24525             /**
24526              * @event move
24527              * Fires after the component is moved.
24528              * @param {Ext.Component} this
24529              * @param {Number} x The new x position
24530              * @param {Number} y The new y position
24531              */
24532             'move'
24533         );
24534
24535         me.getId();
24536
24537         me.mons = [];
24538         me.additionalCls = [];
24539         me.renderData = me.renderData || {};
24540         me.renderSelectors = me.renderSelectors || {};
24541
24542         if (me.plugins) {
24543             me.plugins = [].concat(me.plugins);
24544             me.constructPlugins();
24545         }
24546
24547         me.initComponent();
24548
24549         // ititComponent gets a chance to change the id property before registering
24550         Ext.ComponentManager.register(me);
24551
24552         // Dont pass the config so that it is not applied to 'this' again
24553         me.mixins.observable.constructor.call(me);
24554         me.mixins.state.constructor.call(me, config);
24555
24556         // Save state on resize.
24557         this.addStateEvents('resize');
24558
24559         // Move this into Observable?
24560         if (me.plugins) {
24561             me.plugins = [].concat(me.plugins);
24562             for (i = 0, len = me.plugins.length; i < len; i++) {
24563                 me.plugins[i] = me.initPlugin(me.plugins[i]);
24564             }
24565         }
24566
24567         me.loader = me.getLoader();
24568
24569         if (me.renderTo) {
24570             me.render(me.renderTo);
24571             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
24572             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
24573             // implications to afterRender so we cannot do that.
24574         }
24575
24576         if (me.autoShow) {
24577             me.show();
24578         }
24579
24580     },
24581
24582     initComponent: function () {
24583         // This is called again here to allow derived classes to add plugin configs to the
24584         // plugins array before calling down to this, the base initComponent.
24585         this.constructPlugins();
24586     },
24587
24588     /**
24589      * The supplied default state gathering method for the AbstractComponent class.
24590      *
24591      * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
24592      * state.
24593      *
24594      * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
24595      * to the result if this basic state is to be saved.
24596      *
24597      * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
24598      * configured for the document.
24599      *
24600      * @return {Object}
24601      */
24602     getState: function() {
24603         var me = this,
24604             layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
24605             state = {
24606                 collapsed: me.collapsed
24607             },
24608             width = me.width,
24609             height = me.height,
24610             cm = me.collapseMemento,
24611             anchors;
24612
24613         // If a Panel-local collapse has taken place, use remembered values as the dimensions.
24614         // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
24615         if (me.collapsed && cm) {
24616             if (Ext.isDefined(cm.data.width)) {
24617                 width = cm.width;
24618             }
24619             if (Ext.isDefined(cm.data.height)) {
24620                 height = cm.height;
24621             }
24622         }
24623
24624         // If we have flex, only store the perpendicular dimension.
24625         if (layout && me.flex) {
24626             state.flex = me.flex;
24627             if (layout.perpendicularPrefix) {
24628                 state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
24629             } else {
24630             }
24631         }
24632         // If we have anchor, only store dimensions which are *not* being anchored
24633         else if (layout && me.anchor) {
24634             state.anchor = me.anchor;
24635             anchors = me.anchor.split(' ').concat(null);
24636             if (!anchors[0]) {
24637                 if (me.width) {
24638                     state.width = width;
24639                 }
24640             }
24641             if (!anchors[1]) {
24642                 if (me.height) {
24643                     state.height = height;
24644                 }
24645             }
24646         }
24647         // Store dimensions.
24648         else {
24649             if (me.width) {
24650                 state.width = width;
24651             }
24652             if (me.height) {
24653                 state.height = height;
24654             }
24655         }
24656
24657         // Don't save dimensions if they are unchanged from the original configuration.
24658         if (state.width == me.initialConfig.width) {
24659             delete state.width;
24660         }
24661         if (state.height == me.initialConfig.height) {
24662             delete state.height;
24663         }
24664
24665         // If a Box layout was managing the perpendicular dimension, don't save that dimension
24666         if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
24667             delete state[layout.perpendicularPrefix];
24668         }
24669         return state;
24670     },
24671
24672     show: Ext.emptyFn,
24673
24674     animate: function(animObj) {
24675         var me = this,
24676             to;
24677
24678         animObj = animObj || {};
24679         to = animObj.to || {};
24680
24681         if (Ext.fx.Manager.hasFxBlock(me.id)) {
24682             return me;
24683         }
24684         // Special processing for animating Component dimensions.
24685         if (!animObj.dynamic && (to.height || to.width)) {
24686             var curWidth = me.getWidth(),
24687                 w = curWidth,
24688                 curHeight = me.getHeight(),
24689                 h = curHeight,
24690                 needsResize = false;
24691
24692             if (to.height && to.height > curHeight) {
24693                 h = to.height;
24694                 needsResize = true;
24695             }
24696             if (to.width && to.width > curWidth) {
24697                 w = to.width;
24698                 needsResize = true;
24699             }
24700
24701             // If any dimensions are being increased, we must resize the internal structure
24702             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
24703             // The animation will then progressively reveal the larger content.
24704             if (needsResize) {
24705                 var clearWidth = !Ext.isNumber(me.width),
24706                     clearHeight = !Ext.isNumber(me.height);
24707
24708                 me.componentLayout.childrenChanged = true;
24709                 me.setSize(w, h, me.ownerCt);
24710                 me.el.setSize(curWidth, curHeight);
24711                 if (clearWidth) {
24712                     delete me.width;
24713                 }
24714                 if (clearHeight) {
24715                     delete me.height;
24716                 }
24717             }
24718         }
24719         return me.mixins.animate.animate.apply(me, arguments);
24720     },
24721
24722     /**
24723      * This method finds the topmost active layout who's processing will eventually determine the size and position of
24724      * this Component.
24725      *
24726      * This method is useful when dynamically adding Components into Containers, and some processing must take place
24727      * after the final sizing and positioning of the Component has been performed.
24728      *
24729      * @return {Ext.Component}
24730      */
24731     findLayoutController: function() {
24732         return this.findParentBy(function(c) {
24733             // Return true if we are at the root of the Container tree
24734             // or this Container's layout is busy but the next one up is not.
24735             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
24736         });
24737     },
24738
24739     onShow : function() {
24740         // Layout if needed
24741         var needsLayout = this.needsLayout;
24742         if (Ext.isObject(needsLayout)) {
24743             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
24744         }
24745     },
24746
24747     constructPlugin: function(plugin) {
24748         if (plugin.ptype && typeof plugin.init != 'function') {
24749             plugin.cmp = this;
24750             plugin = Ext.PluginManager.create(plugin);
24751         }
24752         else if (typeof plugin == 'string') {
24753             plugin = Ext.PluginManager.create({
24754                 ptype: plugin,
24755                 cmp: this
24756             });
24757         }
24758         return plugin;
24759     },
24760
24761     /**
24762      * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
24763      * appropriate instances.
24764      */
24765     constructPlugins: function() {
24766         var me = this,
24767             plugins = me.plugins,
24768             i, len;
24769
24770         if (plugins) {
24771             for (i = 0, len = plugins.length; i < len; i++) {
24772                 // this just returns already-constructed plugin instances...
24773                 plugins[i] = me.constructPlugin(plugins[i]);
24774             }
24775         }
24776     },
24777
24778     // @private
24779     initPlugin : function(plugin) {
24780         plugin.init(this);
24781
24782         return plugin;
24783     },
24784
24785     /**
24786      * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
24787      * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
24788      * document.body
24789      */
24790     doAutoRender: function() {
24791         var me = this;
24792         if (me.floating) {
24793             me.render(document.body);
24794         } else {
24795             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
24796         }
24797     },
24798
24799     // @private
24800     render : function(container, position) {
24801         var me = this;
24802
24803         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
24804
24805             // Flag set during the render process.
24806             // It can be used to inhibit event-driven layout calls during the render phase
24807             me.rendering = true;
24808
24809             // If this.el is defined, we want to make sure we are dealing with
24810             // an Ext Element.
24811             if (me.el) {
24812                 me.el = Ext.get(me.el);
24813             }
24814
24815             // Perform render-time processing for floating Components
24816             if (me.floating) {
24817                 me.onFloatRender();
24818             }
24819
24820             container = me.initContainer(container);
24821
24822             me.onRender(container, position);
24823
24824             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
24825             // This means DISPLAY, VISIBILITY or OFFSETS.
24826             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
24827
24828             if (me.overCls) {
24829                 me.el.hover(me.addOverCls, me.removeOverCls, me);
24830             }
24831
24832             me.fireEvent('render', me);
24833
24834             me.initContent();
24835
24836             me.afterRender(container);
24837             me.fireEvent('afterrender', me);
24838
24839             me.initEvents();
24840
24841             if (me.hidden) {
24842                 // Hiding during the render process should not perform any ancillary
24843                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
24844                 // So just make the element hidden according to the configured hideMode
24845                 me.el.hide();
24846             }
24847
24848             if (me.disabled) {
24849                 // pass silent so the event doesn't fire the first time.
24850                 me.disable(true);
24851             }
24852
24853             // Delete the flag once the rendering is done.
24854             delete me.rendering;
24855         }
24856         return me;
24857     },
24858
24859     // @private
24860     onRender : function(container, position) {
24861         var me = this,
24862             el = me.el,
24863             styles = me.initStyles(),
24864             renderTpl, renderData, i;
24865
24866         position = me.getInsertPosition(position);
24867
24868         if (!el) {
24869             if (position) {
24870                 el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
24871             }
24872             else {
24873                 el = Ext.DomHelper.append(container, me.getElConfig(), true);
24874             }
24875         }
24876         else if (me.allowDomMove !== false) {
24877             if (position) {
24878                 container.dom.insertBefore(el.dom, position);
24879             } else {
24880                 container.dom.appendChild(el.dom);
24881             }
24882         }
24883
24884         if (Ext.scopeResetCSS && !me.ownerCt) {
24885             // If this component's el is the body element, we add the reset class to the html tag
24886             if (el.dom == Ext.getBody().dom) {
24887                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
24888             }
24889             else {
24890                 // Else we wrap this element in an element that adds the reset class.
24891                 me.resetEl = el.wrap({
24892                     cls: Ext.baseCSSPrefix + 'reset'
24893                 });
24894             }
24895         }
24896
24897         me.setUI(me.ui);
24898
24899         el.addCls(me.initCls());
24900         el.setStyle(styles);
24901
24902         // Here we check if the component has a height set through style or css.
24903         // If it does then we set the this.height to that value and it won't be
24904         // considered an auto height component
24905         // if (this.height === undefined) {
24906         //     var height = el.getHeight();
24907         //     // This hopefully means that the panel has an explicit height set in style or css
24908         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
24909         //         this.height = height;
24910         //     }
24911         // }
24912
24913         me.el = el;
24914
24915         me.initFrame();
24916
24917         renderTpl = me.initRenderTpl();
24918         if (renderTpl) {
24919             renderData = me.initRenderData();
24920             renderTpl.append(me.getTargetEl(), renderData);
24921         }
24922
24923         me.applyRenderSelectors();
24924
24925         me.rendered = true;
24926     },
24927
24928     // @private
24929     afterRender : function() {
24930         var me = this,
24931             pos,
24932             xy;
24933
24934         me.getComponentLayout();
24935
24936         // Set the size if a size is configured, or if this is the outermost Container.
24937         // Also, if this is a collapsed Panel, it needs an initial component layout
24938         // to lay out its header so that it can have a height determined.
24939         if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
24940             me.setSize(me.width, me.height);
24941         } else {
24942             // It is expected that child items be rendered before this method returns and
24943             // the afterrender event fires. Since we aren't going to do the layout now, we
24944             // must render the child items. This is handled implicitly above in the layout
24945             // caused by setSize.
24946             me.renderChildren();
24947         }
24948
24949         // For floaters, calculate x and y if they aren't defined by aligning
24950         // the sized element to the center of either the container or the ownerCt
24951         if (me.floating && (me.x === undefined || me.y === undefined)) {
24952             if (me.floatParent) {
24953                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
24954                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
24955             } else {
24956                 xy = me.el.getAlignToXY(me.container, 'c-c');
24957                 pos = me.container.translatePoints(xy[0], xy[1]);
24958             }
24959             me.x = me.x === undefined ? pos.left: me.x;
24960             me.y = me.y === undefined ? pos.top: me.y;
24961         }
24962
24963         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
24964             me.setPosition(me.x, me.y);
24965         }
24966
24967         if (me.styleHtmlContent) {
24968             me.getTargetEl().addCls(me.styleHtmlCls);
24969         }
24970     },
24971
24972     /**
24973      * @private
24974      * Called by Component#doAutoRender
24975      *
24976      * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
24977      *
24978      * Components added in ths way will not participate in any layout, but will be rendered
24979      * upon first show in the way that {@link Ext.window.Window Window}s are.
24980      */
24981     registerFloatingItem: function(cmp) {
24982         var me = this;
24983         if (!me.floatingItems) {
24984             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
24985         }
24986         me.floatingItems.register(cmp);
24987     },
24988
24989     renderChildren: function () {
24990         var me = this,
24991             layout = me.getComponentLayout();
24992
24993         me.suspendLayout = true;
24994         layout.renderChildren();
24995         delete me.suspendLayout;
24996     },
24997
24998     frameCls: Ext.baseCSSPrefix + 'frame',
24999
25000     frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
25001
25002     frameElementCls: {
25003         tl: [],
25004         tc: [],
25005         tr: [],
25006         ml: [],
25007         mc: [],
25008         mr: [],
25009         bl: [],
25010         bc: [],
25011         br: []
25012     },
25013
25014     frameTpl: [
25015         '<tpl if="top">',
25016             '<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>',
25017                 '<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>',
25018                     '<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>',
25019                 '<tpl if="right"></div></tpl>',
25020             '<tpl if="left"></div></tpl>',
25021         '</tpl>',
25022         '<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>',
25023             '<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>',
25024                 '<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>',
25025             '<tpl if="right"></div></tpl>',
25026         '<tpl if="left"></div></tpl>',
25027         '<tpl if="bottom">',
25028             '<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>',
25029                 '<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>',
25030                     '<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>',
25031                 '<tpl if="right"></div></tpl>',
25032             '<tpl if="left"></div></tpl>',
25033         '</tpl>'
25034     ],
25035
25036     frameTableTpl: [
25037         '<table><tbody>',
25038             '<tpl if="top">',
25039                 '<tr>',
25040                     '<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>',
25041                     '<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>',
25042                     '<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>',
25043                 '</tr>',
25044             '</tpl>',
25045             '<tr>',
25046                 '<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>',
25047                 '<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>',
25048                 '<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>',
25049             '</tr>',
25050             '<tpl if="bottom">',
25051                 '<tr>',
25052                     '<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>',
25053                     '<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>',
25054                     '<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>',
25055                 '</tr>',
25056             '</tpl>',
25057         '</tbody></table>'
25058     ],
25059
25060     /**
25061      * @private
25062      */
25063     initFrame : function() {
25064         if (Ext.supports.CSS3BorderRadius) {
25065             return false;
25066         }
25067
25068         var me = this,
25069             frameInfo = me.getFrameInfo(),
25070             frameWidth = frameInfo.width,
25071             frameTpl = me.getFrameTpl(frameInfo.table),
25072             frameGenId;
25073
25074         if (me.frame) {
25075             // since we render id's into the markup and id's NEED to be unique, we have a
25076             // simple strategy for numbering their generations.
25077             me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
25078             frameGenId = me.id + '-frame' + frameGenId;
25079
25080             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
25081             frameTpl.insertFirst(me.el, Ext.apply({}, {
25082                 fgid:       frameGenId,
25083                 ui:         me.ui,
25084                 uiCls:      me.uiCls,
25085                 frameCls:   me.frameCls,
25086                 baseCls:    me.baseCls,
25087                 frameWidth: frameWidth,
25088                 top:        !!frameInfo.top,
25089                 left:       !!frameInfo.left,
25090                 right:      !!frameInfo.right,
25091                 bottom:     !!frameInfo.bottom
25092             }, me.getFramePositions(frameInfo)));
25093
25094             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
25095             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
25096
25097             // Clean out the childEls for the old frame elements (the majority of the els)
25098             me.removeChildEls(function (c) {
25099                 return c.id && me.frameIdRegex.test(c.id);
25100             });
25101
25102             // Add the childEls for each of the new frame elements
25103             Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
25104                 me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
25105             });
25106         }
25107     },
25108
25109     updateFrame: function() {
25110         if (Ext.supports.CSS3BorderRadius) {
25111             return false;
25112         }
25113
25114         var me = this,
25115             wasTable = this.frameSize && this.frameSize.table,
25116             oldFrameTL = this.frameTL,
25117             oldFrameBL = this.frameBL,
25118             oldFrameML = this.frameML,
25119             oldFrameMC = this.frameMC,
25120             newMCClassName;
25121
25122         this.initFrame();
25123
25124         if (oldFrameMC) {
25125             if (me.frame) {
25126                 // Reapply render selectors
25127                 delete me.frameTL;
25128                 delete me.frameTC;
25129                 delete me.frameTR;
25130                 delete me.frameML;
25131                 delete me.frameMC;
25132                 delete me.frameMR;
25133                 delete me.frameBL;
25134                 delete me.frameBC;
25135                 delete me.frameBR;
25136                 this.applyRenderSelectors();
25137
25138                 // Store the class names set on the new mc
25139                 newMCClassName = this.frameMC.dom.className;
25140
25141                 // Replace the new mc with the old mc
25142                 oldFrameMC.insertAfter(this.frameMC);
25143                 this.frameMC.remove();
25144
25145                 // Restore the reference to the old frame mc as the framebody
25146                 this.frameBody = this.frameMC = oldFrameMC;
25147
25148                 // Apply the new mc classes to the old mc element
25149                 oldFrameMC.dom.className = newMCClassName;
25150
25151                 // Remove the old framing
25152                 if (wasTable) {
25153                     me.el.query('> table')[1].remove();
25154                 }
25155                 else {
25156                     if (oldFrameTL) {
25157                         oldFrameTL.remove();
25158                     }
25159                     if (oldFrameBL) {
25160                         oldFrameBL.remove();
25161                     }
25162                     oldFrameML.remove();
25163                 }
25164             }
25165             else {
25166                 // We were framed but not anymore. Move all content from the old frame to the body
25167
25168             }
25169         }
25170         else if (me.frame) {
25171             this.applyRenderSelectors();
25172         }
25173     },
25174
25175     getFrameInfo: function() {
25176         if (Ext.supports.CSS3BorderRadius) {
25177             return false;
25178         }
25179
25180         var me = this,
25181             left = me.el.getStyle('background-position-x'),
25182             top = me.el.getStyle('background-position-y'),
25183             info, frameInfo = false, max;
25184
25185         // Some browsers dont support background-position-x and y, so for those
25186         // browsers let's split background-position into two parts.
25187         if (!left && !top) {
25188             info = me.el.getStyle('background-position').split(' ');
25189             left = info[0];
25190             top = info[1];
25191         }
25192
25193         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
25194         // the background position of this.el from the css to indicate to IE that this component needs
25195         // framing. We parse it here and change the markup accordingly.
25196         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
25197             max = Math.max;
25198
25199             frameInfo = {
25200                 // Table markup starts with 110, div markup with 100.
25201                 table: left.substr(0, 3) == '110',
25202
25203                 // Determine if we are dealing with a horizontal or vertical component
25204                 vertical: top.substr(0, 3) == '110',
25205
25206                 // Get and parse the different border radius sizes
25207                 top:    max(left.substr(3, 2), left.substr(5, 2)),
25208                 right:  max(left.substr(5, 2), top.substr(3, 2)),
25209                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
25210                 left:   max(top.substr(5, 2), left.substr(3, 2))
25211             };
25212
25213             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
25214
25215             // Just to be sure we set the background image of the el to none.
25216             me.el.setStyle('background-image', 'none');
25217         }
25218
25219         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
25220         // This way IE can't figure out what sizes to use and thus framing can't work.
25221         if (me.frame === true && !frameInfo) {
25222         }
25223
25224         me.frame = me.frame || !!frameInfo;
25225         me.frameSize = frameInfo || false;
25226
25227         return frameInfo;
25228     },
25229
25230     getFramePositions: function(frameInfo) {
25231         var me = this,
25232             frameWidth = frameInfo.width,
25233             dock = me.dock,
25234             positions, tc, bc, ml, mr;
25235
25236         if (frameInfo.vertical) {
25237             tc = '0 -' + (frameWidth * 0) + 'px';
25238             bc = '0 -' + (frameWidth * 1) + 'px';
25239
25240             if (dock && dock == "right") {
25241                 tc = 'right -' + (frameWidth * 0) + 'px';
25242                 bc = 'right -' + (frameWidth * 1) + 'px';
25243             }
25244
25245             positions = {
25246                 tl: '0 -' + (frameWidth * 0) + 'px',
25247                 tr: '0 -' + (frameWidth * 1) + 'px',
25248                 bl: '0 -' + (frameWidth * 2) + 'px',
25249                 br: '0 -' + (frameWidth * 3) + 'px',
25250
25251                 ml: '-' + (frameWidth * 1) + 'px 0',
25252                 mr: 'right 0',
25253
25254                 tc: tc,
25255                 bc: bc
25256             };
25257         } else {
25258             ml = '-' + (frameWidth * 0) + 'px 0';
25259             mr = 'right 0';
25260
25261             if (dock && dock == "bottom") {
25262                 ml = 'left bottom';
25263                 mr = 'right bottom';
25264             }
25265
25266             positions = {
25267                 tl: '0 -' + (frameWidth * 2) + 'px',
25268                 tr: 'right -' + (frameWidth * 3) + 'px',
25269                 bl: '0 -' + (frameWidth * 4) + 'px',
25270                 br: 'right -' + (frameWidth * 5) + 'px',
25271
25272                 ml: ml,
25273                 mr: mr,
25274
25275                 tc: '0 -' + (frameWidth * 0) + 'px',
25276                 bc: '0 -' + (frameWidth * 1) + 'px'
25277             };
25278         }
25279
25280         return positions;
25281     },
25282
25283     /**
25284      * @private
25285      */
25286     getFrameTpl : function(table) {
25287         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
25288     },
25289
25290     /**
25291      * Creates an array of class names from the configurations to add to this Component's `el` on render.
25292      *
25293      * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
25294      *
25295      * @return {String[]} An array of class names with which the Component's element will be rendered.
25296      * @private
25297      */
25298     initCls: function() {
25299         var me = this,
25300             cls = [];
25301
25302         cls.push(me.baseCls);
25303
25304         if (Ext.isDefined(me.cmpCls)) {
25305             if (Ext.isDefined(Ext.global.console)) {
25306                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
25307             }
25308             me.componentCls = me.cmpCls;
25309             delete me.cmpCls;
25310         }
25311
25312         if (me.componentCls) {
25313             cls.push(me.componentCls);
25314         } else {
25315             me.componentCls = me.baseCls;
25316         }
25317         if (me.cls) {
25318             cls.push(me.cls);
25319             delete me.cls;
25320         }
25321
25322         return cls.concat(me.additionalCls);
25323     },
25324
25325     /**
25326      * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
25327      * uiCls set on the component and rename them so they include the new UI
25328      * @param {String} ui The new UI for the component
25329      */
25330     setUI: function(ui) {
25331         var me = this,
25332             oldUICls = Ext.Array.clone(me.uiCls),
25333             newUICls = [],
25334             classes = [],
25335             cls,
25336             i;
25337
25338         //loop through all exisiting uiCls and update the ui in them
25339         for (i = 0; i < oldUICls.length; i++) {
25340             cls = oldUICls[i];
25341
25342             classes = classes.concat(me.removeClsWithUI(cls, true));
25343             newUICls.push(cls);
25344         }
25345
25346         if (classes.length) {
25347             me.removeCls(classes);
25348         }
25349
25350         //remove the UI from the element
25351         me.removeUIFromElement();
25352
25353         //set the UI
25354         me.ui = ui;
25355
25356         //add the new UI to the elemend
25357         me.addUIToElement();
25358
25359         //loop through all exisiting uiCls and update the ui in them
25360         classes = [];
25361         for (i = 0; i < newUICls.length; i++) {
25362             cls = newUICls[i];
25363             classes = classes.concat(me.addClsWithUI(cls, true));
25364         }
25365
25366         if (classes.length) {
25367             me.addCls(classes);
25368         }
25369     },
25370
25371     /**
25372      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
25373      * component.
25374      * @param {String/String[]} cls A string or an array of strings to add to the uiCls
25375      * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
25376      */
25377     addClsWithUI: function(cls, skip) {
25378         var me = this,
25379             classes = [],
25380             i;
25381
25382         if (!Ext.isArray(cls)) {
25383             cls = [cls];
25384         }
25385
25386         for (i = 0; i < cls.length; i++) {
25387             if (cls[i] && !me.hasUICls(cls[i])) {
25388                 me.uiCls = Ext.Array.clone(me.uiCls);
25389                 me.uiCls.push(cls[i]);
25390
25391                 classes = classes.concat(me.addUIClsToElement(cls[i]));
25392             }
25393         }
25394
25395         if (skip !== true) {
25396             me.addCls(classes);
25397         }
25398
25399         return classes;
25400     },
25401
25402     /**
25403      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
25404      * elements of this component.
25405      * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
25406      */
25407     removeClsWithUI: function(cls, skip) {
25408         var me = this,
25409             classes = [],
25410             i;
25411
25412         if (!Ext.isArray(cls)) {
25413             cls = [cls];
25414         }
25415
25416         for (i = 0; i < cls.length; i++) {
25417             if (cls[i] && me.hasUICls(cls[i])) {
25418                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
25419
25420                 classes = classes.concat(me.removeUIClsFromElement(cls[i]));
25421             }
25422         }
25423
25424         if (skip !== true) {
25425             me.removeCls(classes);
25426         }
25427
25428         return classes;
25429     },
25430
25431     /**
25432      * Checks if there is currently a specified uiCls
25433      * @param {String} cls The cls to check
25434      */
25435     hasUICls: function(cls) {
25436         var me = this,
25437             uiCls = me.uiCls || [];
25438
25439         return Ext.Array.contains(uiCls, cls);
25440     },
25441
25442     /**
25443      * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
25444      * than just the components element.
25445      * @param {String} ui The UI to remove from the element
25446      */
25447     addUIClsToElement: function(cls, force) {
25448         var me = this,
25449             result = [],
25450             frameElementCls = me.frameElementCls;
25451
25452         result.push(Ext.baseCSSPrefix + cls);
25453         result.push(me.baseCls + '-' + cls);
25454         result.push(me.baseCls + '-' + me.ui + '-' + cls);
25455
25456         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
25457             // define each element of the frame
25458             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25459                 classes, i, j, el;
25460
25461             // loop through each of them, and if they are defined add the ui
25462             for (i = 0; i < els.length; i++) {
25463                 el = me['frame' + els[i].toUpperCase()];
25464                 classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
25465                 if (el && el.dom) {
25466                     el.addCls(classes);
25467                 } else {
25468                     for (j = 0; j < classes.length; j++) {
25469                         if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
25470                             frameElementCls[els[i]].push(classes[j]);
25471                         }
25472                     }
25473                 }
25474             }
25475         }
25476
25477         me.frameElementCls = frameElementCls;
25478
25479         return result;
25480     },
25481
25482     /**
25483      * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
25484      * will be: `this.baseCls + '-' + ui`
25485      * @param {String} ui The UI to add to the element
25486      */
25487     removeUIClsFromElement: function(cls, force) {
25488         var me = this,
25489             result = [],
25490             frameElementCls = me.frameElementCls;
25491
25492         result.push(Ext.baseCSSPrefix + cls);
25493         result.push(me.baseCls + '-' + cls);
25494         result.push(me.baseCls + '-' + me.ui + '-' + cls);
25495
25496         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
25497             // define each element of the frame
25498             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25499                 i, el;
25500             cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
25501             // loop through each of them, and if they are defined add the ui
25502             for (i = 0; i < els.length; i++) {
25503                 el = me['frame' + els[i].toUpperCase()];
25504                 if (el && el.dom) {
25505                     el.removeCls(cls);
25506                 } else {
25507                     Ext.Array.remove(frameElementCls[els[i]], cls);
25508                 }
25509             }
25510         }
25511
25512         me.frameElementCls = frameElementCls;
25513
25514         return result;
25515     },
25516
25517     /**
25518      * Method which adds a specified UI to the components element.
25519      * @private
25520      */
25521     addUIToElement: function(force) {
25522         var me = this,
25523             frameElementCls = me.frameElementCls;
25524
25525         me.addCls(me.baseCls + '-' + me.ui);
25526
25527         if (me.frame && !Ext.supports.CSS3BorderRadius) {
25528             // define each element of the frame
25529             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25530                 i, el, cls;
25531
25532             // loop through each of them, and if they are defined add the ui
25533             for (i = 0; i < els.length; i++) {
25534                 el = me['frame' + els[i].toUpperCase()];
25535                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
25536                 if (el) {
25537                     el.addCls(cls);
25538                 } else {
25539                     if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
25540                         frameElementCls[els[i]].push(cls);
25541                     }
25542                 }
25543             }
25544         }
25545     },
25546
25547     /**
25548      * Method which removes a specified UI from the components element.
25549      * @private
25550      */
25551     removeUIFromElement: function() {
25552         var me = this,
25553             frameElementCls = me.frameElementCls;
25554
25555         me.removeCls(me.baseCls + '-' + me.ui);
25556
25557         if (me.frame && !Ext.supports.CSS3BorderRadius) {
25558             // define each element of the frame
25559             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
25560                 i, j, el, cls;
25561
25562             // loop through each of them, and if they are defined add the ui
25563             for (i = 0; i < els.length; i++) {
25564                 el = me['frame' + els[i].toUpperCase()];
25565                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
25566
25567                 if (el) {
25568                     el.removeCls(cls);
25569                 } else {
25570                     Ext.Array.remove(frameElementCls[els[i]], cls);
25571                 }
25572             }
25573         }
25574     },
25575
25576     getElConfig : function() {
25577         if (Ext.isString(this.autoEl)) {
25578             this.autoEl = {
25579                 tag: this.autoEl
25580             };
25581         }
25582
25583         var result = this.autoEl || {tag: 'div'};
25584         result.id = this.id;
25585         return result;
25586     },
25587
25588     /**
25589      * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
25590      * insertBefore.
25591      * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
25592      * component before.
25593      * @return {HTMLElement} DOM element that you can use in the insertBefore
25594      */
25595     getInsertPosition: function(position) {
25596         // Convert the position to an element to insert before
25597         if (position !== undefined) {
25598             if (Ext.isNumber(position)) {
25599                 position = this.container.dom.childNodes[position];
25600             }
25601             else {
25602                 position = Ext.getDom(position);
25603             }
25604         }
25605
25606         return position;
25607     },
25608
25609     /**
25610      * Adds ctCls to container.
25611      * @return {Ext.Element} The initialized container
25612      * @private
25613      */
25614     initContainer: function(container) {
25615         var me = this;
25616
25617         // If you render a component specifying the el, we get the container
25618         // of the el, and make sure we dont move the el around in the dom
25619         // during the render
25620         if (!container && me.el) {
25621             container = me.el.dom.parentNode;
25622             me.allowDomMove = false;
25623         }
25624
25625         me.container = Ext.get(container);
25626
25627         if (me.ctCls) {
25628             me.container.addCls(me.ctCls);
25629         }
25630
25631         return me.container;
25632     },
25633
25634     /**
25635      * Initialized the renderData to be used when rendering the renderTpl.
25636      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
25637      * @private
25638      */
25639     initRenderData: function() {
25640         var me = this;
25641
25642         return Ext.applyIf(me.renderData, {
25643             id: me.id,
25644             ui: me.ui,
25645             uiCls: me.uiCls,
25646             baseCls: me.baseCls,
25647             componentCls: me.componentCls,
25648             frame: me.frame
25649         });
25650     },
25651
25652     /**
25653      * @private
25654      */
25655     getTpl: function(name) {
25656         var me = this,
25657             prototype = me.self.prototype,
25658             ownerPrototype,
25659             tpl;
25660
25661         if (me.hasOwnProperty(name)) {
25662             tpl = me[name];
25663             if (tpl && !(tpl instanceof Ext.XTemplate)) {
25664                 me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
25665             }
25666
25667             return me[name];
25668         }
25669
25670         if (!(prototype[name] instanceof Ext.XTemplate)) {
25671             ownerPrototype = prototype;
25672
25673             do {
25674                 if (ownerPrototype.hasOwnProperty(name)) {
25675                     tpl = ownerPrototype[name];
25676                     if (tpl && !(tpl instanceof Ext.XTemplate)) {
25677                         ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
25678                         break;
25679                     }
25680                 }
25681
25682                 ownerPrototype = ownerPrototype.superclass;
25683             } while (ownerPrototype);
25684         }
25685
25686         return prototype[name];
25687     },
25688
25689     /**
25690      * Initializes the renderTpl.
25691      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
25692      * @private
25693      */
25694     initRenderTpl: function() {
25695         return this.getTpl('renderTpl');
25696     },
25697
25698     /**
25699      * Converts style definitions to String.
25700      * @return {String} A CSS style string with style, padding, margin and border.
25701      * @private
25702      */
25703     initStyles: function() {
25704         var style = {},
25705             me = this,
25706             Element = Ext.Element;
25707
25708         if (Ext.isString(me.style)) {
25709             style = Element.parseStyles(me.style);
25710         } else {
25711             style = Ext.apply({}, me.style);
25712         }
25713
25714         // Convert the padding, margin and border properties from a space seperated string
25715         // into a proper style string
25716         if (me.padding !== undefined) {
25717             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
25718         }
25719
25720         if (me.margin !== undefined) {
25721             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
25722         }
25723
25724         delete me.style;
25725         return style;
25726     },
25727
25728     /**
25729      * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
25730      * @private
25731      */
25732     initContent: function() {
25733         var me = this,
25734             target = me.getTargetEl(),
25735             contentEl,
25736             pre;
25737
25738         if (me.html) {
25739             target.update(Ext.DomHelper.markup(me.html));
25740             delete me.html;
25741         }
25742
25743         if (me.contentEl) {
25744             contentEl = Ext.get(me.contentEl);
25745             pre = Ext.baseCSSPrefix;
25746             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
25747             target.appendChild(contentEl.dom);
25748         }
25749
25750         if (me.tpl) {
25751             // Make sure this.tpl is an instantiated XTemplate
25752             if (!me.tpl.isTemplate) {
25753                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
25754             }
25755
25756             if (me.data) {
25757                 me.tpl[me.tplWriteMode](target, me.data);
25758                 delete me.data;
25759             }
25760         }
25761     },
25762
25763     // @private
25764     initEvents : function() {
25765         var me = this,
25766             afterRenderEvents = me.afterRenderEvents,
25767             el,
25768             property,
25769             fn = function(listeners){
25770                 me.mon(el, listeners);
25771             };
25772         if (afterRenderEvents) {
25773             for (property in afterRenderEvents) {
25774                 if (afterRenderEvents.hasOwnProperty(property)) {
25775                     el = me[property];
25776                     if (el && el.on) {
25777                         Ext.each(afterRenderEvents[property], fn);
25778                     }
25779                 }
25780             }
25781         }
25782     },
25783
25784     /**
25785      * Adds each argument passed to this method to the {@link #childEls} array.
25786      */
25787     addChildEls: function () {
25788         var me = this,
25789             childEls = me.childEls || (me.childEls = []);
25790
25791         childEls.push.apply(childEls, arguments);
25792     },
25793
25794     /**
25795      * Removes items in the childEls array based on the return value of a supplied test function. The function is called
25796      * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
25797      * kept.
25798      * @param {Function} testFn The test function.
25799      */
25800     removeChildEls: function (testFn) {
25801         var me = this,
25802             old = me.childEls,
25803             keepers = (me.childEls = []),
25804             n, i, cel;
25805
25806         for (i = 0, n = old.length; i < n; ++i) {
25807             cel = old[i];
25808             if (!testFn(cel)) {
25809                 keepers.push(cel);
25810             }
25811         }
25812     },
25813
25814     /**
25815      * Sets references to elements inside the component. This applies {@link #renderSelectors}
25816      * as well as {@link #childEls}.
25817      * @private
25818      */
25819     applyRenderSelectors: function() {
25820         var me = this,
25821             childEls = me.childEls,
25822             selectors = me.renderSelectors,
25823             el = me.el,
25824             dom = el.dom,
25825             baseId, childName, childId, i, selector;
25826
25827         if (childEls) {
25828             baseId = me.id + '-';
25829             for (i = childEls.length; i--; ) {
25830                 childName = childId = childEls[i];
25831                 if (typeof(childName) != 'string') {
25832                     childId = childName.id || (baseId + childName.itemId);
25833                     childName = childName.name;
25834                 } else {
25835                     childId = baseId + childId;
25836                 }
25837
25838                 // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
25839                 // we know the el's are children of our el we use getById instead:
25840                 me[childName] = el.getById(childId);
25841             }
25842         }
25843
25844         // We still support renderSelectors. There are a few places in the framework that
25845         // need them and they are a documented part of the API. In fact, we support mixing
25846         // childEls and renderSelectors (no reason not to).
25847         if (selectors) {
25848             for (selector in selectors) {
25849                 if (selectors.hasOwnProperty(selector) && selectors[selector]) {
25850                     me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
25851                 }
25852             }
25853         }
25854     },
25855
25856     /**
25857      * Tests whether this Component matches the selector string.
25858      * @param {String} selector The selector string to test against.
25859      * @return {Boolean} True if this Component matches the selector.
25860      */
25861     is: function(selector) {
25862         return Ext.ComponentQuery.is(this, selector);
25863     },
25864
25865     /**
25866      * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
25867      *
25868      * Example:
25869      *
25870      *     var owningTabPanel = grid.up('tabpanel');
25871      *
25872      * @param {String} [selector] The simple selector to test.
25873      * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
25874      */
25875     up: function(selector) {
25876         var result = this.ownerCt;
25877         if (selector) {
25878             for (; result; result = result.ownerCt) {
25879                 if (Ext.ComponentQuery.is(result, selector)) {
25880                     return result;
25881                 }
25882             }
25883         }
25884         return result;
25885     },
25886
25887     /**
25888      * Returns the next sibling of this Component.
25889      *
25890      * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
25891      *
25892      * May also be refered to as **`next()`**
25893      *
25894      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
25895      * {@link #nextNode}
25896      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
25897      * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
25898      * Returns null if there is no matching sibling.
25899      */
25900     nextSibling: function(selector) {
25901         var o = this.ownerCt, it, last, idx, c;
25902         if (o) {
25903             it = o.items;
25904             idx = it.indexOf(this) + 1;
25905             if (idx) {
25906                 if (selector) {
25907                     for (last = it.getCount(); idx < last; idx++) {
25908                         if ((c = it.getAt(idx)).is(selector)) {
25909                             return c;
25910                         }
25911                     }
25912                 } else {
25913                     if (idx < it.getCount()) {
25914                         return it.getAt(idx);
25915                     }
25916                 }
25917             }
25918         }
25919         return null;
25920     },
25921
25922     /**
25923      * Returns the previous sibling of this Component.
25924      *
25925      * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
25926      * selector.
25927      *
25928      * May also be refered to as **`prev()`**
25929      *
25930      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
25931      * {@link #previousNode}
25932      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
25933      * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
25934      * Returns null if there is no matching sibling.
25935      */
25936     previousSibling: function(selector) {
25937         var o = this.ownerCt, it, idx, c;
25938         if (o) {
25939             it = o.items;
25940             idx = it.indexOf(this);
25941             if (idx != -1) {
25942                 if (selector) {
25943                     for (--idx; idx >= 0; idx--) {
25944                         if ((c = it.getAt(idx)).is(selector)) {
25945                             return c;
25946                         }
25947                     }
25948                 } else {
25949                     if (idx) {
25950                         return it.getAt(--idx);
25951                     }
25952                 }
25953             }
25954         }
25955         return null;
25956     },
25957
25958     /**
25959      * Returns the previous node in the Component tree in tree traversal order.
25960      *
25961      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
25962      * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
25963      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
25964      * @return {Ext.Component} The previous node (or the previous node which matches the selector).
25965      * Returns null if there is no matching node.
25966      */
25967     previousNode: function(selector, includeSelf) {
25968         var node = this,
25969             result,
25970             it, len, i;
25971
25972         // If asked to include self, test me
25973         if (includeSelf && node.is(selector)) {
25974             return node;
25975         }
25976
25977         result = this.prev(selector);
25978         if (result) {
25979             return result;
25980         }
25981
25982         if (node.ownerCt) {
25983             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
25984                 if (it[i].query) {
25985                     result = it[i].query(selector);
25986                     result = result[result.length - 1];
25987                     if (result) {
25988                         return result;
25989                     }
25990                 }
25991             }
25992             return node.ownerCt.previousNode(selector, true);
25993         }
25994     },
25995
25996     /**
25997      * Returns the next node in the Component tree in tree traversal order.
25998      *
25999      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
26000      * tree to attempt to find a match. Contrast with {@link #nextSibling}.
26001      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
26002      * @return {Ext.Component} The next node (or the next node which matches the selector).
26003      * Returns null if there is no matching node.
26004      */
26005     nextNode: function(selector, includeSelf) {
26006         var node = this,
26007             result,
26008             it, len, i;
26009
26010         // If asked to include self, test me
26011         if (includeSelf && node.is(selector)) {
26012             return node;
26013         }
26014
26015         result = this.next(selector);
26016         if (result) {
26017             return result;
26018         }
26019
26020         if (node.ownerCt) {
26021             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
26022                 if (it[i].down) {
26023                     result = it[i].down(selector);
26024                     if (result) {
26025                         return result;
26026                     }
26027                 }
26028             }
26029             return node.ownerCt.nextNode(selector);
26030         }
26031     },
26032
26033     /**
26034      * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
26035      * @return {String}
26036      */
26037     getId : function() {
26038         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
26039     },
26040
26041     getItemId : function() {
26042         return this.itemId || this.id;
26043     },
26044
26045     /**
26046      * Retrieves the top level element representing this component.
26047      * @return {Ext.core.Element}
26048      */
26049     getEl : function() {
26050         return this.el;
26051     },
26052
26053     /**
26054      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
26055      * @private
26056      */
26057     getTargetEl: function() {
26058         return this.frameBody || this.el;
26059     },
26060
26061     /**
26062      * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
26063      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
26064      *
26065      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26066      * determination of inherited xtypes.**
26067      *
26068      * For a list of all available xtypes, see the {@link Ext.Component} header.
26069      *
26070      * Example usage:
26071      *
26072      *     var t = new Ext.form.field.Text();
26073      *     var isText = t.isXType('textfield');        // true
26074      *     var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
26075      *     var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
26076      *
26077      * @param {String} xtype The xtype to check for this Component
26078      * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
26079      * check whether this Component is descended from the xtype.
26080      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
26081      */
26082     isXType: function(xtype, shallow) {
26083         //assume a string by default
26084         if (Ext.isFunction(xtype)) {
26085             xtype = xtype.xtype;
26086             //handle being passed the class, e.g. Ext.Component
26087         } else if (Ext.isObject(xtype)) {
26088             xtype = xtype.statics().xtype;
26089             //handle being passed an instance
26090         }
26091
26092         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
26093     },
26094
26095     /**
26096      * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
26097      * {@link Ext.Component} header.
26098      *
26099      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
26100      * determination of inherited xtypes.**
26101      *
26102      * Example usage:
26103      *
26104      *     var t = new Ext.form.field.Text();
26105      *     alert(t.getXTypes());  // alerts 'component/field/textfield'
26106      *
26107      * @return {String} The xtype hierarchy string
26108      */
26109     getXTypes: function() {
26110         var self = this.self,
26111             xtypes, parentPrototype, parentXtypes;
26112
26113         if (!self.xtypes) {
26114             xtypes = [];
26115             parentPrototype = this;
26116
26117             while (parentPrototype) {
26118                 parentXtypes = parentPrototype.xtypes;
26119
26120                 if (parentXtypes !== undefined) {
26121                     xtypes.unshift.apply(xtypes, parentXtypes);
26122                 }
26123
26124                 parentPrototype = parentPrototype.superclass;
26125             }
26126
26127             self.xtypeChain = xtypes;
26128             self.xtypes = xtypes.join('/');
26129         }
26130
26131         return self.xtypes;
26132     },
26133
26134     /**
26135      * Update the content area of a component.
26136      * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
26137      * it will use this argument as data to populate the template. If this component was not configured with a template,
26138      * the components content area will be updated via Ext.Element update
26139      * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
26140      * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
26141      * scripts have finished loading
26142      */
26143     update : function(htmlOrData, loadScripts, cb) {
26144         var me = this;
26145
26146         if (me.tpl && !Ext.isString(htmlOrData)) {
26147             me.data = htmlOrData;
26148             if (me.rendered) {
26149                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
26150             }
26151         } else {
26152             me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
26153             if (me.rendered) {
26154                 me.getTargetEl().update(me.html, loadScripts, cb);
26155             }
26156         }
26157
26158         if (me.rendered) {
26159             me.doComponentLayout();
26160         }
26161     },
26162
26163     /**
26164      * Convenience function to hide or show this component by boolean.
26165      * @param {Boolean} visible True to show, false to hide
26166      * @return {Ext.Component} this
26167      */
26168     setVisible : function(visible) {
26169         return this[visible ? 'show': 'hide']();
26170     },
26171
26172     /**
26173      * Returns true if this component is visible.
26174      *
26175      * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
26176      * determine whether this Component is truly visible to the user.
26177      *
26178      * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
26179      * dynamically laid out UIs in a hidden Container before showing them.
26180      *
26181      * @return {Boolean} True if this component is visible, false otherwise.
26182      */
26183     isVisible: function(deep) {
26184         var me = this,
26185             child = me,
26186             visible = !me.hidden,
26187             ancestor = me.ownerCt;
26188
26189         // Clear hiddenOwnerCt property
26190         me.hiddenAncestor = false;
26191         if (me.destroyed) {
26192             return false;
26193         }
26194
26195         if (deep && visible && me.rendered && ancestor) {
26196             while (ancestor) {
26197                 // If any ancestor is hidden, then this is hidden.
26198                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
26199                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
26200                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
26201                 if (ancestor.hidden || (ancestor.collapsed &&
26202                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
26203                     // Store hiddenOwnerCt property if needed
26204                     me.hiddenAncestor = ancestor;
26205                     visible = false;
26206                     break;
26207                 }
26208                 child = ancestor;
26209                 ancestor = ancestor.ownerCt;
26210             }
26211         }
26212         return visible;
26213     },
26214
26215     /**
26216      * Enable the component
26217      * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
26218      */
26219     enable: function(silent) {
26220         var me = this;
26221
26222         if (me.rendered) {
26223             me.el.removeCls(me.disabledCls);
26224             me.el.dom.disabled = false;
26225             me.onEnable();
26226         }
26227
26228         me.disabled = false;
26229
26230         if (silent !== true) {
26231             me.fireEvent('enable', me);
26232         }
26233
26234         return me;
26235     },
26236
26237     /**
26238      * Disable the component.
26239      * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
26240      */
26241     disable: function(silent) {
26242         var me = this;
26243
26244         if (me.rendered) {
26245             me.el.addCls(me.disabledCls);
26246             me.el.dom.disabled = true;
26247             me.onDisable();
26248         }
26249
26250         me.disabled = true;
26251
26252         if (silent !== true) {
26253             me.fireEvent('disable', me);
26254         }
26255
26256         return me;
26257     },
26258
26259     // @private
26260     onEnable: function() {
26261         if (this.maskOnDisable) {
26262             this.el.unmask();
26263         }
26264     },
26265
26266     // @private
26267     onDisable : function() {
26268         if (this.maskOnDisable) {
26269             this.el.mask();
26270         }
26271     },
26272
26273     /**
26274      * Method to determine whether this Component is currently disabled.
26275      * @return {Boolean} the disabled state of this Component.
26276      */
26277     isDisabled : function() {
26278         return this.disabled;
26279     },
26280
26281     /**
26282      * Enable or disable the component.
26283      * @param {Boolean} disabled True to disable.
26284      */
26285     setDisabled : function(disabled) {
26286         return this[disabled ? 'disable': 'enable']();
26287     },
26288
26289     /**
26290      * Method to determine whether this Component is currently set to hidden.
26291      * @return {Boolean} the hidden state of this Component.
26292      */
26293     isHidden : function() {
26294         return this.hidden;
26295     },
26296
26297     /**
26298      * Adds a CSS class to the top level element representing this component.
26299      * @param {String} cls The CSS class name to add
26300      * @return {Ext.Component} Returns the Component to allow method chaining.
26301      */
26302     addCls : function(className) {
26303         var me = this;
26304         if (!className) {
26305             return me;
26306         }
26307         if (!Ext.isArray(className)){
26308             className = className.replace(me.trimRe, '').split(me.spacesRe);
26309         }
26310         if (me.rendered) {
26311             me.el.addCls(className);
26312         }
26313         else {
26314             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
26315         }
26316         return me;
26317     },
26318
26319     /**
26320      * Adds a CSS class to the top level element representing this component.
26321      * @param {String} cls The CSS class name to add
26322      * @return {Ext.Component} Returns the Component to allow method chaining.
26323      */
26324     addClass : function() {
26325         return this.addCls.apply(this, arguments);
26326     },
26327
26328     /**
26329      * Removes a CSS class from the top level element representing this component.
26330      * @param {Object} className
26331      * @return {Ext.Component} Returns the Component to allow method chaining.
26332      */
26333     removeCls : function(className) {
26334         var me = this;
26335
26336         if (!className) {
26337             return me;
26338         }
26339         if (!Ext.isArray(className)){
26340             className = className.replace(me.trimRe, '').split(me.spacesRe);
26341         }
26342         if (me.rendered) {
26343             me.el.removeCls(className);
26344         }
26345         else if (me.additionalCls.length) {
26346             Ext.each(className, function(cls) {
26347                 Ext.Array.remove(me.additionalCls, cls);
26348             });
26349         }
26350         return me;
26351     },
26352
26353
26354     addOverCls: function() {
26355         var me = this;
26356         if (!me.disabled) {
26357             me.el.addCls(me.overCls);
26358         }
26359     },
26360
26361     removeOverCls: function() {
26362         this.el.removeCls(this.overCls);
26363     },
26364
26365     addListener : function(element, listeners, scope, options) {
26366         var me = this,
26367             fn,
26368             option;
26369
26370         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
26371             if (options.element) {
26372                 fn = listeners;
26373
26374                 listeners = {};
26375                 listeners[element] = fn;
26376                 element = options.element;
26377                 if (scope) {
26378                     listeners.scope = scope;
26379                 }
26380
26381                 for (option in options) {
26382                     if (options.hasOwnProperty(option)) {
26383                         if (me.eventOptionsRe.test(option)) {
26384                             listeners[option] = options[option];
26385                         }
26386                     }
26387                 }
26388             }
26389
26390             // At this point we have a variable called element,
26391             // and a listeners object that can be passed to on
26392             if (me[element] && me[element].on) {
26393                 me.mon(me[element], listeners);
26394             } else {
26395                 me.afterRenderEvents = me.afterRenderEvents || {};
26396                 if (!me.afterRenderEvents[element]) {
26397                     me.afterRenderEvents[element] = [];
26398                 }
26399                 me.afterRenderEvents[element].push(listeners);
26400             }
26401         }
26402
26403         return me.mixins.observable.addListener.apply(me, arguments);
26404     },
26405
26406     // inherit docs
26407     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
26408         var me = this,
26409             element = managedListener.options ? managedListener.options.element : null;
26410
26411         if (element) {
26412             element = me[element];
26413             if (element && element.un) {
26414                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
26415                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
26416                     if (!isClear) {
26417                         Ext.Array.remove(me.managedListeners, managedListener);
26418                     }
26419                 }
26420             }
26421         } else {
26422             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
26423         }
26424     },
26425
26426     /**
26427      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
26428      * @return {Ext.container.Container} the Container which owns this Component.
26429      */
26430     getBubbleTarget : function() {
26431         return this.ownerCt;
26432     },
26433
26434     /**
26435      * Method to determine whether this Component is floating.
26436      * @return {Boolean} the floating state of this component.
26437      */
26438     isFloating : function() {
26439         return this.floating;
26440     },
26441
26442     /**
26443      * Method to determine whether this Component is draggable.
26444      * @return {Boolean} the draggable state of this component.
26445      */
26446     isDraggable : function() {
26447         return !!this.draggable;
26448     },
26449
26450     /**
26451      * Method to determine whether this Component is droppable.
26452      * @return {Boolean} the droppable state of this component.
26453      */
26454     isDroppable : function() {
26455         return !!this.droppable;
26456     },
26457
26458     /**
26459      * @private
26460      * Method to manage awareness of when components are added to their
26461      * respective Container, firing an added event.
26462      * References are established at add time rather than at render time.
26463      * @param {Ext.container.Container} container Container which holds the component
26464      * @param {Number} pos Position at which the component was added
26465      */
26466     onAdded : function(container, pos) {
26467         this.ownerCt = container;
26468         this.fireEvent('added', this, container, pos);
26469     },
26470
26471     /**
26472      * @private
26473      * Method to manage awareness of when components are removed from their
26474      * respective Container, firing an removed event. References are properly
26475      * cleaned up after removing a component from its owning container.
26476      */
26477     onRemoved : function() {
26478         var me = this;
26479
26480         me.fireEvent('removed', me, me.ownerCt);
26481         delete me.ownerCt;
26482     },
26483
26484     // @private
26485     beforeDestroy : Ext.emptyFn,
26486     // @private
26487     // @private
26488     onResize : Ext.emptyFn,
26489
26490     /**
26491      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
26492      * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
26493      *
26494      * @param {Number/String/Object} width The new width to set. This may be one of:
26495      *
26496      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26497      *   - A String used to set the CSS width style.
26498      *   - A size object in the format `{width: widthValue, height: heightValue}`.
26499      *   - `undefined` to leave the width unchanged.
26500      *
26501      * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
26502      * This may be one of:
26503      *
26504      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26505      *   - A String used to set the CSS height style. Animation may **not** be used.
26506      *   - `undefined` to leave the height unchanged.
26507      *
26508      * @return {Ext.Component} this
26509      */
26510     setSize : function(width, height) {
26511         var me = this,
26512             layoutCollection;
26513
26514         // support for standard size objects
26515         if (Ext.isObject(width)) {
26516             height = width.height;
26517             width  = width.width;
26518         }
26519
26520         // Constrain within configured maxima
26521         if (Ext.isNumber(width)) {
26522             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
26523         }
26524         if (Ext.isNumber(height)) {
26525             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
26526         }
26527
26528         if (!me.rendered || !me.isVisible()) {
26529             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
26530             if (me.hiddenAncestor) {
26531                 layoutCollection = me.hiddenAncestor.layoutOnShow;
26532                 layoutCollection.remove(me);
26533                 layoutCollection.add(me);
26534             }
26535             me.needsLayout = {
26536                 width: width,
26537                 height: height,
26538                 isSetSize: true
26539             };
26540             if (!me.rendered) {
26541                 me.width  = (width !== undefined) ? width : me.width;
26542                 me.height = (height !== undefined) ? height : me.height;
26543             }
26544             return me;
26545         }
26546         me.doComponentLayout(width, height, true);
26547
26548         return me;
26549     },
26550
26551     isFixedWidth: function() {
26552         var me = this,
26553             layoutManagedWidth = me.layoutManagedWidth;
26554
26555         if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
26556             return true;
26557         }
26558         if (layoutManagedWidth == 2) {
26559             return false;
26560         }
26561         return (me.ownerCt && me.ownerCt.isFixedWidth());
26562     },
26563
26564     isFixedHeight: function() {
26565         var me = this,
26566             layoutManagedHeight = me.layoutManagedHeight;
26567
26568         if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
26569             return true;
26570         }
26571         if (layoutManagedHeight == 2) {
26572             return false;
26573         }
26574         return (me.ownerCt && me.ownerCt.isFixedHeight());
26575     },
26576
26577     setCalculatedSize : function(width, height, callingContainer) {
26578         var me = this,
26579             layoutCollection;
26580
26581         // support for standard size objects
26582         if (Ext.isObject(width)) {
26583             callingContainer = width.ownerCt;
26584             height = width.height;
26585             width  = width.width;
26586         }
26587
26588         // Constrain within configured maxima
26589         if (Ext.isNumber(width)) {
26590             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
26591         }
26592         if (Ext.isNumber(height)) {
26593             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
26594         }
26595
26596         if (!me.rendered || !me.isVisible()) {
26597             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
26598             if (me.hiddenAncestor) {
26599                 layoutCollection = me.hiddenAncestor.layoutOnShow;
26600                 layoutCollection.remove(me);
26601                 layoutCollection.add(me);
26602             }
26603             me.needsLayout = {
26604                 width: width,
26605                 height: height,
26606                 isSetSize: false,
26607                 ownerCt: callingContainer
26608             };
26609             return me;
26610         }
26611         me.doComponentLayout(width, height, false, callingContainer);
26612
26613         return me;
26614     },
26615
26616     /**
26617      * This method needs to be called whenever you change something on this component that requires the Component's
26618      * layout to be recalculated.
26619      * @param {Object} width
26620      * @param {Object} height
26621      * @param {Object} isSetSize
26622      * @param {Object} callingContainer
26623      * @return {Ext.container.Container} this
26624      */
26625     doComponentLayout : function(width, height, isSetSize, callingContainer) {
26626         var me = this,
26627             componentLayout = me.getComponentLayout(),
26628             lastComponentSize = componentLayout.lastComponentSize || {
26629                 width: undefined,
26630                 height: undefined
26631             };
26632
26633         // collapsed state is not relevant here, so no testing done.
26634         // Only Panels have a collapse method, and that just sets the width/height such that only
26635         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
26636         if (me.rendered && componentLayout) {
26637             // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
26638             if (!Ext.isDefined(width)) {
26639                 if (me.isFixedWidth()) {
26640                     width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
26641                 }
26642             }
26643             // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
26644             if (!Ext.isDefined(height)) {
26645                 if (me.isFixedHeight()) {
26646                     height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
26647                 }
26648             }
26649
26650             if (isSetSize) {
26651                 me.width = width;
26652                 me.height = height;
26653             }
26654
26655             componentLayout.layout(width, height, isSetSize, callingContainer);
26656         }
26657
26658         return me;
26659     },
26660
26661     /**
26662      * Forces this component to redo its componentLayout.
26663      */
26664     forceComponentLayout: function () {
26665         this.doComponentLayout();
26666     },
26667
26668     // @private
26669     setComponentLayout : function(layout) {
26670         var currentLayout = this.componentLayout;
26671         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
26672             currentLayout.setOwner(null);
26673         }
26674         this.componentLayout = layout;
26675         layout.setOwner(this);
26676     },
26677
26678     getComponentLayout : function() {
26679         var me = this;
26680
26681         if (!me.componentLayout || !me.componentLayout.isLayout) {
26682             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
26683         }
26684         return me.componentLayout;
26685     },
26686
26687     /**
26688      * Occurs after componentLayout is run.
26689      * @param {Number} adjWidth The box-adjusted width that was set
26690      * @param {Number} adjHeight The box-adjusted height that was set
26691      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
26692      * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
26693      */
26694     afterComponentLayout: function(width, height, isSetSize, callingContainer) {
26695         var me = this,
26696             layout = me.componentLayout,
26697             oldSize = me.preLayoutSize;
26698
26699         ++me.componentLayoutCounter;
26700         if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
26701             me.fireEvent('resize', me, width, height);
26702         }
26703     },
26704
26705     /**
26706      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
26707      * being executed.
26708      * @param {Number} adjWidth The box-adjusted width that was set
26709      * @param {Number} adjHeight The box-adjusted height that was set
26710      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
26711      * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
26712      */
26713     beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
26714         this.preLayoutSize = this.componentLayout.lastComponentSize;
26715         return true;
26716     },
26717
26718     /**
26719      * Sets the left and top of the component. To set the page XY position instead, use
26720      * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
26721      * @param {Number} left The new left
26722      * @param {Number} top The new top
26723      * @return {Ext.Component} this
26724      */
26725     setPosition : function(x, y) {
26726         var me = this;
26727
26728         if (Ext.isObject(x)) {
26729             y = x.y;
26730             x = x.x;
26731         }
26732
26733         if (!me.rendered) {
26734             return me;
26735         }
26736
26737         if (x !== undefined || y !== undefined) {
26738             me.el.setBox(x, y);
26739             me.onPosition(x, y);
26740             me.fireEvent('move', me, x, y);
26741         }
26742         return me;
26743     },
26744
26745     /**
26746      * @private
26747      * Called after the component is moved, this method is empty by default but can be implemented by any
26748      * subclass that needs to perform custom logic after a move occurs.
26749      * @param {Number} x The new x position
26750      * @param {Number} y The new y position
26751      */
26752     onPosition: Ext.emptyFn,
26753
26754     /**
26755      * Sets the width of the component. This method fires the {@link #resize} event.
26756      *
26757      * @param {Number} width The new width to setThis may be one of:
26758      *
26759      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26760      *   - A String used to set the CSS width style.
26761      *
26762      * @return {Ext.Component} this
26763      */
26764     setWidth : function(width) {
26765         return this.setSize(width);
26766     },
26767
26768     /**
26769      * Sets the height of the component. This method fires the {@link #resize} event.
26770      *
26771      * @param {Number} height The new height to set. This may be one of:
26772      *
26773      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
26774      *   - A String used to set the CSS height style.
26775      *   - _undefined_ to leave the height unchanged.
26776      *
26777      * @return {Ext.Component} this
26778      */
26779     setHeight : function(height) {
26780         return this.setSize(undefined, height);
26781     },
26782
26783     /**
26784      * Gets the current size of the component's underlying element.
26785      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
26786      */
26787     getSize : function() {
26788         return this.el.getSize();
26789     },
26790
26791     /**
26792      * Gets the current width of the component's underlying element.
26793      * @return {Number}
26794      */
26795     getWidth : function() {
26796         return this.el.getWidth();
26797     },
26798
26799     /**
26800      * Gets the current height of the component's underlying element.
26801      * @return {Number}
26802      */
26803     getHeight : function() {
26804         return this.el.getHeight();
26805     },
26806
26807     /**
26808      * Gets the {@link Ext.ComponentLoader} for this Component.
26809      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
26810      */
26811     getLoader: function(){
26812         var me = this,
26813             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
26814             loader = me.loader || autoLoad;
26815
26816         if (loader) {
26817             if (!loader.isLoader) {
26818                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
26819                     target: me,
26820                     autoLoad: autoLoad
26821                 }, loader));
26822             } else {
26823                 loader.setTarget(me);
26824             }
26825             return me.loader;
26826
26827         }
26828         return null;
26829     },
26830
26831     /**
26832      * This method allows you to show or hide a LoadMask on top of this component.
26833      *
26834      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
26835      * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
26836      * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
26837      * setting this to true on a Panel will cause only the body to be masked.
26838      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
26839      */
26840     setLoading : function(load, targetEl) {
26841         var me = this,
26842             config;
26843
26844         if (me.rendered) {
26845             if (load !== false && !me.collapsed) {
26846                 if (Ext.isObject(load)) {
26847                     config = load;
26848                 }
26849                 else if (Ext.isString(load)) {
26850                     config = {msg: load};
26851                 }
26852                 else {
26853                     config = {};
26854                 }
26855                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
26856                 me.loadMask.show();
26857             } else if (me.loadMask) {
26858                 Ext.destroy(me.loadMask);
26859                 me.loadMask = null;
26860             }
26861         }
26862
26863         return me.loadMask;
26864     },
26865
26866     /**
26867      * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
26868      * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
26869      * @param {Object} dock The dock position.
26870      * @param {Boolean} [layoutParent=false] True to re-layout parent.
26871      * @return {Ext.Component} this
26872      */
26873     setDocked : function(dock, layoutParent) {
26874         var me = this;
26875
26876         me.dock = dock;
26877         if (layoutParent && me.ownerCt && me.rendered) {
26878             me.ownerCt.doComponentLayout();
26879         }
26880         return me;
26881     },
26882
26883     onDestroy : function() {
26884         var me = this;
26885
26886         if (me.monitorResize && Ext.EventManager.resizeEvent) {
26887             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
26888         }
26889         // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
26890         Ext.destroy(
26891             me.componentLayout,
26892             me.loadMask,
26893             me.floatingItems
26894         );
26895     },
26896
26897     /**
26898      * Remove any references to elements added via renderSelectors/childEls
26899      * @private
26900      */
26901     cleanElementRefs: function(){
26902         var me = this,
26903             i = 0,
26904             childEls = me.childEls,
26905             selectors = me.renderSelectors,
26906             selector,
26907             name,
26908             len;
26909
26910         if (me.rendered) {
26911             if (childEls) {
26912                 for (len = childEls.length; i < len; ++i) {
26913                     name = childEls[i];
26914                     if (typeof(name) != 'string') {
26915                         name = name.name;
26916                     }
26917                     delete me[name];
26918                 }
26919             }
26920
26921             if (selectors) {
26922                 for (selector in selectors) {
26923                     if (selectors.hasOwnProperty(selector)) {
26924                         delete me[selector];
26925                     }
26926                 }
26927             }
26928         }
26929         delete me.rendered;
26930         delete me.el;
26931         delete me.frameBody;
26932     },
26933
26934     /**
26935      * Destroys the Component.
26936      */
26937     destroy : function() {
26938         var me = this;
26939
26940         if (!me.isDestroyed) {
26941             if (me.fireEvent('beforedestroy', me) !== false) {
26942                 me.destroying = true;
26943                 me.beforeDestroy();
26944
26945                 if (me.floating) {
26946                     delete me.floatParent;
26947                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
26948                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
26949                     if (me.zIndexManager) {
26950                         me.zIndexManager.unregister(me);
26951                     }
26952                 } else if (me.ownerCt && me.ownerCt.remove) {
26953                     me.ownerCt.remove(me, false);
26954                 }
26955
26956                 me.onDestroy();
26957
26958                 // Attempt to destroy all plugins
26959                 Ext.destroy(me.plugins);
26960
26961                 if (me.rendered) {
26962                     me.el.remove();
26963                 }
26964
26965                 me.fireEvent('destroy', me);
26966                 Ext.ComponentManager.unregister(me);
26967
26968                 me.mixins.state.destroy.call(me);
26969
26970                 me.clearListeners();
26971                 // make sure we clean up the element references after removing all events
26972                 me.cleanElementRefs();
26973                 me.destroying = false;
26974                 me.isDestroyed = true;
26975             }
26976         }
26977     },
26978
26979     /**
26980      * Retrieves a plugin by its pluginId which has been bound to this component.
26981      * @param {Object} pluginId
26982      * @return {Ext.AbstractPlugin} plugin instance.
26983      */
26984     getPlugin: function(pluginId) {
26985         var i = 0,
26986             plugins = this.plugins,
26987             ln = plugins.length;
26988         for (; i < ln; i++) {
26989             if (plugins[i].pluginId === pluginId) {
26990                 return plugins[i];
26991             }
26992         }
26993     },
26994
26995     /**
26996      * Determines whether this component is the descendant of a particular container.
26997      * @param {Ext.Container} container
26998      * @return {Boolean} True if it is.
26999      */
27000     isDescendantOf: function(container) {
27001         return !!this.findParentBy(function(p){
27002             return p === container;
27003         });
27004     }
27005 }, function() {
27006     this.createAlias({
27007         on: 'addListener',
27008         prev: 'previousSibling',
27009         next: 'nextSibling'
27010     });
27011 });
27012
27013 /**
27014  * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
27015  *
27016  * This class defines the essential API of plugins as used by Components by defining the following methods:
27017  *
27018  *   - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
27019  *
27020  *     The Component passes itself as the sole parameter.
27021  *
27022  *     Subclasses should set up bidirectional links between the plugin and its client Component here.
27023  *
27024  *   - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
27025  *
27026  *     Use this method to break links between the plugin and the Component and to free any allocated resources.
27027  *
27028  *   - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
27029  *
27030  *   - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
27031  */
27032 Ext.define('Ext.AbstractPlugin', {
27033     disabled: false,
27034
27035     constructor: function(config) {
27036         Ext.apply(this, config);
27037     },
27038
27039     getCmp: function() {
27040         return this.cmp;
27041     },
27042
27043     /**
27044      * @method
27045      * The init method is invoked after initComponent method has been run for the client Component.
27046      *
27047      * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
27048      * links between the plugin and its client Component in their own implementation of this method.
27049      * @param {Ext.Component} client The client Component which owns this plugin.
27050      */
27051     init: Ext.emptyFn,
27052
27053     /**
27054      * @method
27055      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
27056      *
27057      * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
27058      * this method.
27059      */
27060     destroy: Ext.emptyFn,
27061
27062     /**
27063      * The base implementation just sets the plugin's `disabled` flag to `false`
27064      *
27065      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27066      */
27067     enable: function() {
27068         this.disabled = false;
27069     },
27070
27071     /**
27072      * The base implementation just sets the plugin's `disabled` flag to `true`
27073      *
27074      * Plugin subclasses which need more complex processing may implement an overriding implementation.
27075      */
27076     disable: function() {
27077         this.disabled = true;
27078     }
27079 });
27080 /**
27081  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
27082  * to a configured URL, or to a URL specified at request time.
27083  *
27084  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
27085  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
27086  * in the request options object, or an {@link #requestcomplete event listener}.
27087  *
27088  * # File Uploads
27089  *
27090  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27091  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
27092  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
27093  * after the return data has been gathered.
27094  *
27095  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
27096  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
27097  * insert the text unchanged into the document body.
27098  *
27099  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
27100  * `&amp;` etc.
27101  *
27102  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
27103  * responseText property in order to conform to the requirements of event handlers and callbacks.
27104  *
27105  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
27106  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
27107  * packet content.
27108  *
27109  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
27110  */
27111 Ext.define('Ext.data.Connection', {
27112     mixins: {
27113         observable: 'Ext.util.Observable'
27114     },
27115
27116     statics: {
27117         requestId: 0
27118     },
27119
27120     url: null,
27121     async: true,
27122     method: null,
27123     username: '',
27124     password: '',
27125
27126     /**
27127      * @cfg {Boolean} disableCaching
27128      * True to add a unique cache-buster param to GET requests.
27129      */
27130     disableCaching: true,
27131
27132     /**
27133      * @cfg {Boolean} withCredentials
27134      * True to set `withCredentials = true` on the XHR object
27135      */
27136     withCredentials: false,
27137
27138     /**
27139      * @cfg {Boolean} cors
27140      * True to enable CORS support on the XHR object. Currently the only effect of this option
27141      * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
27142      */
27143     cors: false,
27144
27145     /**
27146      * @cfg {String} disableCachingParam
27147      * Change the parameter which is sent went disabling caching through a cache buster.
27148      */
27149     disableCachingParam: '_dc',
27150
27151     /**
27152      * @cfg {Number} timeout
27153      * The timeout in milliseconds to be used for requests.
27154      */
27155     timeout : 30000,
27156
27157     /**
27158      * @cfg {Object} extraParams
27159      * Any parameters to be appended to the request.
27160      */
27161
27162     useDefaultHeader : true,
27163     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
27164     useDefaultXhrHeader : true,
27165     defaultXhrHeader : 'XMLHttpRequest',
27166
27167     constructor : function(config) {
27168         config = config || {};
27169         Ext.apply(this, config);
27170
27171         this.addEvents(
27172             /**
27173              * @event beforerequest
27174              * Fires before a network request is made to retrieve a data object.
27175              * @param {Ext.data.Connection} conn This Connection object.
27176              * @param {Object} options The options config object passed to the {@link #request} method.
27177              */
27178             'beforerequest',
27179             /**
27180              * @event requestcomplete
27181              * Fires if the request was successfully completed.
27182              * @param {Ext.data.Connection} conn This Connection object.
27183              * @param {Object} response The XHR object containing the response data.
27184              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27185              * @param {Object} options The options config object passed to the {@link #request} method.
27186              */
27187             'requestcomplete',
27188             /**
27189              * @event requestexception
27190              * Fires if an error HTTP status was returned from the server.
27191              * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
27192              * for details of HTTP status codes.
27193              * @param {Ext.data.Connection} conn This Connection object.
27194              * @param {Object} response The XHR object containing the response data.
27195              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
27196              * @param {Object} options The options config object passed to the {@link #request} method.
27197              */
27198             'requestexception'
27199         );
27200         this.requests = {};
27201         this.mixins.observable.constructor.call(this);
27202     },
27203
27204     /**
27205      * Sends an HTTP request to a remote server.
27206      *
27207      * **Important:** Ajax server requests are asynchronous, and this call will
27208      * return before the response has been received. Process any returned data
27209      * in a callback function.
27210      *
27211      *     Ext.Ajax.request({
27212      *         url: 'ajax_demo/sample.json',
27213      *         success: function(response, opts) {
27214      *             var obj = Ext.decode(response.responseText);
27215      *             console.dir(obj);
27216      *         },
27217      *         failure: function(response, opts) {
27218      *             console.log('server-side failure with status code ' + response.status);
27219      *         }
27220      *     });
27221      *
27222      * To execute a callback function in the correct scope, use the `scope` option.
27223      *
27224      * @param {Object} options An object which may contain the following properties:
27225      *
27226      * (The options object may also contain any other property which might be needed to perform
27227      * postprocessing in a callback because it is passed to callback functions.)
27228      *
27229      * @param {String/Function} options.url The URL to which to send the request, or a function
27230      * to call which returns a URL string. The scope of the function is specified by the `scope` option.
27231      * Defaults to the configured `url`.
27232      *
27233      * @param {Object/String/Function} options.params An object containing properties which are
27234      * used as parameters to the request, a url encoded string or a function to call to get either. The scope
27235      * of the function is specified by the `scope` option.
27236      *
27237      * @param {String} options.method The HTTP method to use
27238      * for the request. Defaults to the configured method, or if no method was configured,
27239      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
27240      * the method name is case-sensitive and should be all caps.
27241      *
27242      * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
27243      * The callback is called regardless of success or failure and is passed the following parameters:
27244      * @param {Object} options.callback.options The parameter to the request call.
27245      * @param {Boolean} options.callback.success True if the request succeeded.
27246      * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
27247      * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
27248      * accessing elements of the response.
27249      *
27250      * @param {Function} options.success The function to be called upon success of the request.
27251      * The callback is passed the following parameters:
27252      * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
27253      * @param {Object} options.success.options The parameter to the request call.
27254      *
27255      * @param {Function} options.failure The function to be called upon success of the request.
27256      * The callback is passed the following parameters:
27257      * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
27258      * @param {Object} options.failure.options The parameter to the request call.
27259      *
27260      * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
27261      * the callback function. If the `url`, or `params` options were specified as functions from which to
27262      * draw values, then this also serves as the scope for those function calls. Defaults to the browser
27263      * window.
27264      *
27265      * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
27266      * Defaults to 30 seconds.
27267      *
27268      * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
27269      * to pull parameters from.
27270      *
27271      * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
27272      *
27273      * True if the form object is a file upload (will be set automatically if the form was configured
27274      * with **`enctype`** `"multipart/form-data"`).
27275      *
27276      * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
27277      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
27278      * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
27279      * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
27280      * has been gathered.
27281      *
27282      * The server response is parsed by the browser to create the document for the IFRAME. If the
27283      * server is using JSON to send the return object, then the [Content-Type][] header must be set to
27284      * "text/html" in order to tell the browser to insert the text unchanged into the document body.
27285      *
27286      * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
27287      * containing a `responseText` property in order to conform to the requirements of event handlers
27288      * and callbacks.
27289      *
27290      * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
27291      * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
27292      * and parameter values from the packet content.
27293      *
27294      * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
27295      * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
27296      * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
27297      *
27298      * @param {Object} options.headers Request headers to set for the request.
27299      *
27300      * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
27301      * of params for the post data. Any params will be appended to the URL.
27302      *
27303      * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
27304      * instead of params for the post data. Any params will be appended to the URL.
27305      *
27306      * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
27307      *
27308      * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
27309      *
27310      * @return {Object} The request object. This may be used to cancel the request.
27311      */
27312     request : function(options) {
27313         options = options || {};
27314         var me = this,
27315             scope = options.scope || window,
27316             username = options.username || me.username,
27317             password = options.password || me.password || '',
27318             async,
27319             requestOptions,
27320             request,
27321             headers,
27322             xhr;
27323
27324         if (me.fireEvent('beforerequest', me, options) !== false) {
27325
27326             requestOptions = me.setOptions(options, scope);
27327
27328             if (this.isFormUpload(options) === true) {
27329                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
27330                 return null;
27331             }
27332
27333             // if autoabort is set, cancel the current transactions
27334             if (options.autoAbort === true || me.autoAbort) {
27335                 me.abort();
27336             }
27337
27338             // create a connection object
27339
27340             if ((options.cors === true || me.cors === true) && Ext.isIE && Ext.ieVersion >= 8) {
27341                 xhr = new XDomainRequest();
27342             } else {
27343                 xhr = this.getXhrInstance();
27344             }
27345
27346             async = options.async !== false ? (options.async || me.async) : false;
27347
27348             // open the request
27349             if (username) {
27350                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
27351             } else {
27352                 xhr.open(requestOptions.method, requestOptions.url, async);
27353             }
27354
27355             if (options.withCredentials === true || me.withCredentials === true) {
27356                 xhr.withCredentials = true;
27357             }
27358
27359             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
27360
27361             // create the transaction object
27362             request = {
27363                 id: ++Ext.data.Connection.requestId,
27364                 xhr: xhr,
27365                 headers: headers,
27366                 options: options,
27367                 async: async,
27368                 timeout: setTimeout(function() {
27369                     request.timedout = true;
27370                     me.abort(request);
27371                 }, options.timeout || me.timeout)
27372             };
27373             me.requests[request.id] = request;
27374             me.latestId = request.id;
27375             // bind our statechange listener
27376             if (async) {
27377                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
27378             }
27379
27380             if ((options.cors === true || me.cors === true) && Ext.isIE && Ext.ieVersion >= 8) {
27381                 xhr.onload = function() {
27382                     me.onComplete(request);
27383                 }
27384             }
27385
27386             // start the request!
27387             xhr.send(requestOptions.data);
27388             if (!async) {
27389                 return this.onComplete(request);
27390             }
27391             return request;
27392         } else {
27393             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
27394             return null;
27395         }
27396     },
27397
27398     /**
27399      * Uploads a form using a hidden iframe.
27400      * @param {String/HTMLElement/Ext.Element} form The form to upload
27401      * @param {String} url The url to post to
27402      * @param {String} params Any extra parameters to pass
27403      * @param {Object} options The initial options
27404      */
27405     upload: function(form, url, params, options) {
27406         form = Ext.getDom(form);
27407         options = options || {};
27408
27409         var id = Ext.id(),
27410                 frame = document.createElement('iframe'),
27411                 hiddens = [],
27412                 encoding = 'multipart/form-data',
27413                 buf = {
27414                     target: form.target,
27415                     method: form.method,
27416                     encoding: form.encoding,
27417                     enctype: form.enctype,
27418                     action: form.action
27419                 }, hiddenItem;
27420
27421         /*
27422          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
27423          * the frame had been added to the document. It seems this has since been corrected in
27424          * Opera so the behaviour has been reverted, the URL will be set before being added.
27425          */
27426         Ext.fly(frame).set({
27427             id: id,
27428             name: id,
27429             cls: Ext.baseCSSPrefix + 'hide-display',
27430             src: Ext.SSL_SECURE_URL
27431         });
27432
27433         document.body.appendChild(frame);
27434
27435         // This is required so that IE doesn't pop the response up in a new window.
27436         if (document.frames) {
27437            document.frames[id].name = id;
27438         }
27439
27440         Ext.fly(form).set({
27441             target: id,
27442             method: 'POST',
27443             enctype: encoding,
27444             encoding: encoding,
27445             action: url || buf.action
27446         });
27447
27448         // add dynamic params
27449         if (params) {
27450             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
27451                 hiddenItem = document.createElement('input');
27452                 Ext.fly(hiddenItem).set({
27453                     type: 'hidden',
27454                     value: value,
27455                     name: name
27456                 });
27457                 form.appendChild(hiddenItem);
27458                 hiddens.push(hiddenItem);
27459             });
27460         }
27461
27462         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
27463         form.submit();
27464
27465         Ext.fly(form).set(buf);
27466         Ext.each(hiddens, function(h) {
27467             Ext.removeNode(h);
27468         });
27469     },
27470
27471     /**
27472      * @private
27473      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
27474      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
27475      * (or a textarea inside the body). We then clean up by removing the iframe
27476      */
27477     onUploadComplete: function(frame, options) {
27478         var me = this,
27479             // bogus response object
27480             response = {
27481                 responseText: '',
27482                 responseXML: null
27483             }, doc, firstChild;
27484
27485         try {
27486             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
27487             if (doc) {
27488                 if (doc.body) {
27489                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
27490                         response.responseText = firstChild.value;
27491                     } else {
27492                         response.responseText = doc.body.innerHTML;
27493                     }
27494                 }
27495                 //in IE the document may still have a body even if returns XML.
27496                 response.responseXML = doc.XMLDocument || doc;
27497             }
27498         } catch (e) {
27499         }
27500
27501         me.fireEvent('requestcomplete', me, response, options);
27502
27503         Ext.callback(options.success, options.scope, [response, options]);
27504         Ext.callback(options.callback, options.scope, [options, true, response]);
27505
27506         setTimeout(function(){
27507             Ext.removeNode(frame);
27508         }, 100);
27509     },
27510
27511     /**
27512      * Detects whether the form is intended to be used for an upload.
27513      * @private
27514      */
27515     isFormUpload: function(options){
27516         var form = this.getForm(options);
27517         if (form) {
27518             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
27519         }
27520         return false;
27521     },
27522
27523     /**
27524      * Gets the form object from options.
27525      * @private
27526      * @param {Object} options The request options
27527      * @return {HTMLElement} The form, null if not passed
27528      */
27529     getForm: function(options){
27530         return Ext.getDom(options.form) || null;
27531     },
27532
27533     /**
27534      * Sets various options such as the url, params for the request
27535      * @param {Object} options The initial options
27536      * @param {Object} scope The scope to execute in
27537      * @return {Object} The params for the request
27538      */
27539     setOptions: function(options, scope){
27540         var me =  this,
27541             params = options.params || {},
27542             extraParams = me.extraParams,
27543             urlParams = options.urlParams,
27544             url = options.url || me.url,
27545             jsonData = options.jsonData,
27546             method,
27547             disableCache,
27548             data;
27549
27550
27551         // allow params to be a method that returns the params object
27552         if (Ext.isFunction(params)) {
27553             params = params.call(scope, options);
27554         }
27555
27556         // allow url to be a method that returns the actual url
27557         if (Ext.isFunction(url)) {
27558             url = url.call(scope, options);
27559         }
27560
27561         url = this.setupUrl(options, url);
27562
27563
27564         // check for xml or json data, and make sure json data is encoded
27565         data = options.rawData || options.xmlData || jsonData || null;
27566         if (jsonData && !Ext.isPrimitive(jsonData)) {
27567             data = Ext.encode(data);
27568         }
27569
27570         // make sure params are a url encoded string and include any extraParams if specified
27571         if (Ext.isObject(params)) {
27572             params = Ext.Object.toQueryString(params);
27573         }
27574
27575         if (Ext.isObject(extraParams)) {
27576             extraParams = Ext.Object.toQueryString(extraParams);
27577         }
27578
27579         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
27580
27581         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
27582
27583         params = this.setupParams(options, params);
27584
27585         // decide the proper method for this request
27586         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
27587         this.setupMethod(options, method);
27588
27589
27590         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
27591         // if the method is get append date to prevent caching
27592         if (method === 'GET' && disableCache) {
27593             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
27594         }
27595
27596         // if the method is get or there is json/xml data append the params to the url
27597         if ((method == 'GET' || data) && params) {
27598             url = Ext.urlAppend(url, params);
27599             params = null;
27600         }
27601
27602         // allow params to be forced into the url
27603         if (urlParams) {
27604             url = Ext.urlAppend(url, urlParams);
27605         }
27606
27607         return {
27608             url: url,
27609             method: method,
27610             data: data || params || null
27611         };
27612     },
27613
27614     /**
27615      * Template method for overriding url
27616      * @template
27617      * @private
27618      * @param {Object} options
27619      * @param {String} url
27620      * @return {String} The modified url
27621      */
27622     setupUrl: function(options, url){
27623         var form = this.getForm(options);
27624         if (form) {
27625             url = url || form.action;
27626         }
27627         return url;
27628     },
27629
27630
27631     /**
27632      * Template method for overriding params
27633      * @template
27634      * @private
27635      * @param {Object} options
27636      * @param {String} params
27637      * @return {String} The modified params
27638      */
27639     setupParams: function(options, params) {
27640         var form = this.getForm(options),
27641             serializedForm;
27642         if (form && !this.isFormUpload(options)) {
27643             serializedForm = Ext.Element.serializeForm(form);
27644             params = params ? (params + '&' + serializedForm) : serializedForm;
27645         }
27646         return params;
27647     },
27648
27649     /**
27650      * Template method for overriding method
27651      * @template
27652      * @private
27653      * @param {Object} options
27654      * @param {String} method
27655      * @return {String} The modified method
27656      */
27657     setupMethod: function(options, method){
27658         if (this.isFormUpload(options)) {
27659             return 'POST';
27660         }
27661         return method;
27662     },
27663
27664     /**
27665      * Setup all the headers for the request
27666      * @private
27667      * @param {Object} xhr The xhr object
27668      * @param {Object} options The options for the request
27669      * @param {Object} data The data for the request
27670      * @param {Object} params The params for the request
27671      */
27672     setupHeaders: function(xhr, options, data, params){
27673         var me = this,
27674             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
27675             contentType = me.defaultPostHeader,
27676             jsonData = options.jsonData,
27677             xmlData = options.xmlData,
27678             key,
27679             header;
27680
27681         if (!headers['Content-Type'] && (data || params)) {
27682             if (data) {
27683                 if (options.rawData) {
27684                     contentType = 'text/plain';
27685                 } else {
27686                     if (xmlData && Ext.isDefined(xmlData)) {
27687                         contentType = 'text/xml';
27688                     } else if (jsonData && Ext.isDefined(jsonData)) {
27689                         contentType = 'application/json';
27690                     }
27691                 }
27692             }
27693             headers['Content-Type'] = contentType;
27694         }
27695
27696         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
27697             headers['X-Requested-With'] = me.defaultXhrHeader;
27698         }
27699         // set up all the request headers on the xhr object
27700         try{
27701             for (key in headers) {
27702                 if (headers.hasOwnProperty(key)) {
27703                     header = headers[key];
27704                     xhr.setRequestHeader(key, header);
27705                 }
27706
27707             }
27708         } catch(e) {
27709             me.fireEvent('exception', key, header);
27710         }
27711         return headers;
27712     },
27713
27714     /**
27715      * Creates the appropriate XHR transport for the browser.
27716      * @private
27717      */
27718     getXhrInstance: (function(){
27719         var options = [function(){
27720             return new XMLHttpRequest();
27721         }, function(){
27722             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
27723         }, function(){
27724             return new ActiveXObject('MSXML2.XMLHTTP');
27725         }, function(){
27726             return new ActiveXObject('Microsoft.XMLHTTP');
27727         }], i = 0,
27728             len = options.length,
27729             xhr;
27730
27731         for(; i < len; ++i) {
27732             try{
27733                 xhr = options[i];
27734                 xhr();
27735                 break;
27736             }catch(e){}
27737         }
27738         return xhr;
27739     })(),
27740
27741     /**
27742      * Determines whether this object has a request outstanding.
27743      * @param {Object} [request] Defaults to the last transaction
27744      * @return {Boolean} True if there is an outstanding request.
27745      */
27746     isLoading : function(request) {
27747         if (!request) {
27748             request = this.getLatest();
27749         }
27750         if (!(request && request.xhr)) {
27751             return false;
27752         }
27753         // if there is a connection and readyState is not 0 or 4
27754         var state = request.xhr.readyState;
27755         return !(state === 0 || state == 4);
27756     },
27757
27758     /**
27759      * Aborts an active request.
27760      * @param {Object} [request] Defaults to the last request
27761      */
27762     abort : function(request) {
27763         var me = this;
27764
27765         if (!request) {
27766             request = me.getLatest();
27767         }
27768
27769         if (request && me.isLoading(request)) {
27770             /*
27771              * Clear out the onreadystatechange here, this allows us
27772              * greater control, the browser may/may not fire the function
27773              * depending on a series of conditions.
27774              */
27775             request.xhr.onreadystatechange = null;
27776             request.xhr.abort();
27777             me.clearTimeout(request);
27778             if (!request.timedout) {
27779                 request.aborted = true;
27780             }
27781             me.onComplete(request);
27782             me.cleanup(request);
27783         }
27784     },
27785
27786     /**
27787      * Aborts all active requests
27788      */
27789     abortAll: function(){
27790         var requests = this.requests,
27791             id;
27792
27793         for (id in requests) {
27794             if (requests.hasOwnProperty(id)) {
27795                 this.abort(requests[id]);
27796             }
27797         }
27798     },
27799
27800     /**
27801      * Gets the most recent request
27802      * @private
27803      * @return {Object} The request. Null if there is no recent request
27804      */
27805     getLatest: function(){
27806         var id = this.latestId,
27807             request;
27808
27809         if (id) {
27810             request = this.requests[id];
27811         }
27812         return request || null;
27813     },
27814
27815     /**
27816      * Fires when the state of the xhr changes
27817      * @private
27818      * @param {Object} request The request
27819      */
27820     onStateChange : function(request) {
27821         if (request.xhr.readyState == 4) {
27822             this.clearTimeout(request);
27823             this.onComplete(request);
27824             this.cleanup(request);
27825         }
27826     },
27827
27828     /**
27829      * Clears the timeout on the request
27830      * @private
27831      * @param {Object} The request
27832      */
27833     clearTimeout: function(request){
27834         clearTimeout(request.timeout);
27835         delete request.timeout;
27836     },
27837
27838     /**
27839      * Cleans up any left over information from the request
27840      * @private
27841      * @param {Object} The request
27842      */
27843     cleanup: function(request){
27844         request.xhr = null;
27845         delete request.xhr;
27846     },
27847
27848     /**
27849      * To be called when the request has come back from the server
27850      * @private
27851      * @param {Object} request
27852      * @return {Object} The response
27853      */
27854     onComplete : function(request) {
27855         var me = this,
27856             options = request.options,
27857             result,
27858             success,
27859             response;
27860
27861         try {
27862             result = me.parseStatus(request.xhr.status);
27863         } catch (e) {
27864             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
27865             result = {
27866                 success : false,
27867                 isException : false
27868             };
27869         }
27870         success = result.success;
27871
27872         if (success) {
27873             response = me.createResponse(request);
27874             me.fireEvent('requestcomplete', me, response, options);
27875             Ext.callback(options.success, options.scope, [response, options]);
27876         } else {
27877             if (result.isException || request.aborted || request.timedout) {
27878                 response = me.createException(request);
27879             } else {
27880                 response = me.createResponse(request);
27881             }
27882             me.fireEvent('requestexception', me, response, options);
27883             Ext.callback(options.failure, options.scope, [response, options]);
27884         }
27885         Ext.callback(options.callback, options.scope, [options, success, response]);
27886         delete me.requests[request.id];
27887         return response;
27888     },
27889
27890     /**
27891      * Checks if the response status was successful
27892      * @param {Number} status The status code
27893      * @return {Object} An object containing success/status state
27894      */
27895     parseStatus: function(status) {
27896         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
27897         status = status == 1223 ? 204 : status;
27898
27899         var success = (status >= 200 && status < 300) || status == 304,
27900             isException = false;
27901
27902         if (!success) {
27903             switch (status) {
27904                 case 12002:
27905                 case 12029:
27906                 case 12030:
27907                 case 12031:
27908                 case 12152:
27909                 case 13030:
27910                     isException = true;
27911                     break;
27912             }
27913         }
27914         return {
27915             success: success,
27916             isException: isException
27917         };
27918     },
27919
27920     /**
27921      * Creates the response object
27922      * @private
27923      * @param {Object} request
27924      */
27925     createResponse : function(request) {
27926         var xhr = request.xhr,
27927             headers = {},
27928             lines = xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n') : [],
27929             count = lines.length,
27930             line, index, key, value, response;
27931
27932         while (count--) {
27933             line = lines[count];
27934             index = line.indexOf(':');
27935             if(index >= 0) {
27936                 key = line.substr(0, index).toLowerCase();
27937                 if (line.charAt(index + 1) == ' ') {
27938                     ++index;
27939                 }
27940                 headers[key] = line.substr(index + 1);
27941             }
27942         }
27943
27944         request.xhr = null;
27945         delete request.xhr;
27946
27947         response = {
27948             request: request,
27949             requestId : request.id,
27950             status : xhr.status,
27951             statusText : xhr.statusText,
27952             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
27953             getAllResponseHeaders : function(){ return headers; },
27954             responseText : xhr.responseText,
27955             responseXML : xhr.responseXML
27956         };
27957
27958         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
27959         // functions created with getResponseHeader/getAllResponseHeaders
27960         xhr = null;
27961         return response;
27962     },
27963
27964     /**
27965      * Creates the exception object
27966      * @private
27967      * @param {Object} request
27968      */
27969     createException : function(request) {
27970         return {
27971             request : request,
27972             requestId : request.id,
27973             status : request.aborted ? -1 : 0,
27974             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
27975             aborted: request.aborted,
27976             timedout: request.timedout
27977         };
27978     }
27979 });
27980
27981 /**
27982  * @class Ext.Ajax
27983  * @singleton
27984  * @markdown
27985  * @extends Ext.data.Connection
27986
27987 A singleton instance of an {@link Ext.data.Connection}. This class
27988 is used to communicate with your server side code. It can be used as follows:
27989
27990     Ext.Ajax.request({
27991         url: 'page.php',
27992         params: {
27993             id: 1
27994         },
27995         success: function(response){
27996             var text = response.responseText;
27997             // process server response here
27998         }
27999     });
28000
28001 Default options for all requests can be set by changing a property on the Ext.Ajax class:
28002
28003     Ext.Ajax.timeout = 60000; // 60 seconds
28004
28005 Any options specified in the request method for the Ajax request will override any
28006 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
28007 request will be 60 seconds.
28008
28009     Ext.Ajax.timeout = 120000; // 120 seconds
28010     Ext.Ajax.request({
28011         url: 'page.aspx',
28012         timeout: 60000
28013     });
28014
28015 In general, this class will be used for all Ajax requests in your application.
28016 The main reason for creating a separate {@link Ext.data.Connection} is for a
28017 series of requests that share common settings that are different to all other
28018 requests in the application.
28019
28020  */
28021 Ext.define('Ext.Ajax', {
28022     extend: 'Ext.data.Connection',
28023     singleton: true,
28024
28025     /**
28026      * @cfg {String} url @hide
28027      */
28028     /**
28029      * @cfg {Object} extraParams @hide
28030      */
28031     /**
28032      * @cfg {Object} defaultHeaders @hide
28033      */
28034     /**
28035      * @cfg {String} method (Optional) @hide
28036      */
28037     /**
28038      * @cfg {Number} timeout (Optional) @hide
28039      */
28040     /**
28041      * @cfg {Boolean} autoAbort (Optional) @hide
28042      */
28043
28044     /**
28045      * @cfg {Boolean} disableCaching (Optional) @hide
28046      */
28047
28048     /**
28049      * @property {Boolean} disableCaching
28050      * True to add a unique cache-buster param to GET requests. Defaults to true.
28051      */
28052     /**
28053      * @property {String} url
28054      * The default URL to be used for requests to the server.
28055      * If the server receives all requests through one URL, setting this once is easier than
28056      * entering it on every request.
28057      */
28058     /**
28059      * @property {Object} extraParams
28060      * An object containing properties which are used as extra parameters to each request made
28061      * by this object. Session information and other data that you need
28062      * to pass with each request are commonly put here.
28063      */
28064     /**
28065      * @property {Object} defaultHeaders
28066      * An object containing request headers which are added to each request made by this object.
28067      */
28068     /**
28069      * @property {String} method
28070      * The default HTTP method to be used for requests. Note that this is case-sensitive and
28071      * should be all caps (if not set but params are present will use
28072      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
28073      */
28074     /**
28075      * @property {Number} timeout
28076      * The timeout in milliseconds to be used for requests. Defaults to 30000.
28077      */
28078
28079     /**
28080      * @property {Boolean} autoAbort
28081      * Whether a new request should abort any pending requests.
28082      */
28083     autoAbort : false
28084 });
28085 /**
28086  * A class used to load remote content to an Element. Sample usage:
28087  *
28088  *     Ext.get('el').load({
28089  *         url: 'myPage.php',
28090  *         scripts: true,
28091  *         params: {
28092  *             id: 1
28093  *         }
28094  *     });
28095  *
28096  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
28097  * will be used.
28098  */
28099 Ext.define('Ext.ElementLoader', {
28100
28101     /* Begin Definitions */
28102
28103     mixins: {
28104         observable: 'Ext.util.Observable'
28105     },
28106
28107     uses: [
28108         'Ext.data.Connection',
28109         'Ext.Ajax'
28110     ],
28111
28112     statics: {
28113         Renderer: {
28114             Html: function(loader, response, active){
28115                 loader.getTarget().update(response.responseText, active.scripts === true);
28116                 return true;
28117             }
28118         }
28119     },
28120
28121     /* End Definitions */
28122
28123     /**
28124      * @cfg {String} url
28125      * The url to retrieve the content from.
28126      */
28127     url: null,
28128
28129     /**
28130      * @cfg {Object} params
28131      * Any params to be attached to the Ajax request. These parameters will
28132      * be overridden by any params in the load options.
28133      */
28134     params: null,
28135
28136     /**
28137      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
28138      * will not be overridden by any params in the load options.
28139      */
28140     baseParams: null,
28141
28142     /**
28143      * @cfg {Boolean/Object} autoLoad
28144      * True to have the loader make a request as soon as it is created.
28145      * This argument can also be a set of options that will be passed to {@link #load} is called.
28146      */
28147     autoLoad: false,
28148
28149     /**
28150      * @cfg {HTMLElement/Ext.Element/String} target
28151      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
28152      */
28153     target: null,
28154
28155     /**
28156      * @cfg {Boolean/String} loadMask
28157      * True or a string to show when the element is loading.
28158      */
28159     loadMask: false,
28160
28161     /**
28162      * @cfg {Object} ajaxOptions
28163      * Any additional options to be passed to the request, for example timeout or headers.
28164      */
28165     ajaxOptions: null,
28166
28167     /**
28168      * @cfg {Boolean} scripts
28169      * True to parse any inline script tags in the response.
28170      */
28171     scripts: false,
28172
28173     /**
28174      * @cfg {Function} success
28175      * A function to be called when a load request is successful.
28176      * Will be called with the following config parameters:
28177      *
28178      * - this - The ElementLoader instance.
28179      * - response - The response object.
28180      * - options - Ajax options.
28181      */
28182
28183     /**
28184      * @cfg {Function} failure A function to be called when a load request fails.
28185      * Will be called with the following config parameters:
28186      *
28187      * - this - The ElementLoader instance.
28188      * - response - The response object.
28189      * - options - Ajax options.
28190      */
28191
28192     /**
28193      * @cfg {Function} callback A function to be called when a load request finishes.
28194      * Will be called with the following config parameters:
28195      *
28196      * - this - The ElementLoader instance.
28197      * - success - True if successful request.
28198      * - response - The response object.
28199      * - options - Ajax options.
28200      */
28201
28202     /**
28203      * @cfg {Object} scope
28204      * The scope to execute the {@link #success} and {@link #failure} functions in.
28205      */
28206
28207     /**
28208      * @cfg {Function} renderer
28209      * A custom function to render the content to the element. The passed parameters are:
28210      *
28211      * - The loader
28212      * - The response
28213      * - The active request
28214      */
28215
28216     isLoader: true,
28217
28218     constructor: function(config) {
28219         var me = this,
28220             autoLoad;
28221
28222         config = config || {};
28223         Ext.apply(me, config);
28224         me.setTarget(me.target);
28225         me.addEvents(
28226             /**
28227              * @event beforeload
28228              * Fires before a load request is made to the server.
28229              * Returning false from an event listener can prevent the load
28230              * from occurring.
28231              * @param {Ext.ElementLoader} this
28232              * @param {Object} options The options passed to the request
28233              */
28234             'beforeload',
28235
28236             /**
28237              * @event exception
28238              * Fires after an unsuccessful load.
28239              * @param {Ext.ElementLoader} this
28240              * @param {Object} response The response from the server
28241              * @param {Object} options The options passed to the request
28242              */
28243             'exception',
28244
28245             /**
28246              * @event load
28247              * Fires after a successful load.
28248              * @param {Ext.ElementLoader} this
28249              * @param {Object} response The response from the server
28250              * @param {Object} options The options passed to the request
28251              */
28252             'load'
28253         );
28254
28255         // don't pass config because we have already applied it.
28256         me.mixins.observable.constructor.call(me);
28257
28258         if (me.autoLoad) {
28259             autoLoad = me.autoLoad;
28260             if (autoLoad === true) {
28261                 autoLoad = {};
28262             }
28263             me.load(autoLoad);
28264         }
28265     },
28266
28267     /**
28268      * Sets an {@link Ext.Element} as the target of this loader.
28269      * Note that if the target is changed, any active requests will be aborted.
28270      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
28271      */
28272     setTarget: function(target){
28273         var me = this;
28274         target = Ext.get(target);
28275         if (me.target && me.target != target) {
28276             me.abort();
28277         }
28278         me.target = target;
28279     },
28280
28281     /**
28282      * Returns the target of this loader.
28283      * @return {Ext.Component} The target or null if none exists.
28284      */
28285     getTarget: function(){
28286         return this.target || null;
28287     },
28288
28289     /**
28290      * Aborts the active load request
28291      */
28292     abort: function(){
28293         var active = this.active;
28294         if (active !== undefined) {
28295             Ext.Ajax.abort(active.request);
28296             if (active.mask) {
28297                 this.removeMask();
28298             }
28299             delete this.active;
28300         }
28301     },
28302
28303     /**
28304      * Removes the mask on the target
28305      * @private
28306      */
28307     removeMask: function(){
28308         this.target.unmask();
28309     },
28310
28311     /**
28312      * Adds the mask on the target
28313      * @private
28314      * @param {Boolean/Object} mask The mask configuration
28315      */
28316     addMask: function(mask){
28317         this.target.mask(mask === true ? null : mask);
28318     },
28319
28320     /**
28321      * Loads new data from the server.
28322      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
28323      * the class, with the exception of the target option. Note that any options passed to the method will override any
28324      * class defaults.
28325      */
28326     load: function(options) {
28327
28328         options = Ext.apply({}, options);
28329
28330         var me = this,
28331             target = me.target,
28332             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
28333             params = Ext.apply({}, options.params),
28334             ajaxOptions = Ext.apply({}, options.ajaxOptions),
28335             callback = options.callback || me.callback,
28336             scope = options.scope || me.scope || me,
28337             request;
28338
28339         Ext.applyIf(ajaxOptions, me.ajaxOptions);
28340         Ext.applyIf(options, ajaxOptions);
28341
28342         Ext.applyIf(params, me.params);
28343         Ext.apply(params, me.baseParams);
28344
28345         Ext.applyIf(options, {
28346             url: me.url
28347         });
28348
28349
28350         Ext.apply(options, {
28351             scope: me,
28352             params: params,
28353             callback: me.onComplete
28354         });
28355
28356         if (me.fireEvent('beforeload', me, options) === false) {
28357             return;
28358         }
28359
28360         if (mask) {
28361             me.addMask(mask);
28362         }
28363
28364         request = Ext.Ajax.request(options);
28365         me.active = {
28366             request: request,
28367             options: options,
28368             mask: mask,
28369             scope: scope,
28370             callback: callback,
28371             success: options.success || me.success,
28372             failure: options.failure || me.failure,
28373             renderer: options.renderer || me.renderer,
28374             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
28375         };
28376         me.setOptions(me.active, options);
28377     },
28378
28379     /**
28380      * Sets any additional options on the active request
28381      * @private
28382      * @param {Object} active The active request
28383      * @param {Object} options The initial options
28384      */
28385     setOptions: Ext.emptyFn,
28386
28387     /**
28388      * Parses the response after the request completes
28389      * @private
28390      * @param {Object} options Ajax options
28391      * @param {Boolean} success Success status of the request
28392      * @param {Object} response The response object
28393      */
28394     onComplete: function(options, success, response) {
28395         var me = this,
28396             active = me.active,
28397             scope = active.scope,
28398             renderer = me.getRenderer(active.renderer);
28399
28400
28401         if (success) {
28402             success = renderer.call(me, me, response, active);
28403         }
28404
28405         if (success) {
28406             Ext.callback(active.success, scope, [me, response, options]);
28407             me.fireEvent('load', me, response, options);
28408         } else {
28409             Ext.callback(active.failure, scope, [me, response, options]);
28410             me.fireEvent('exception', me, response, options);
28411         }
28412         Ext.callback(active.callback, scope, [me, success, response, options]);
28413
28414         if (active.mask) {
28415             me.removeMask();
28416         }
28417
28418         delete me.active;
28419     },
28420
28421     /**
28422      * Gets the renderer to use
28423      * @private
28424      * @param {String/Function} renderer The renderer to use
28425      * @return {Function} A rendering function to use.
28426      */
28427     getRenderer: function(renderer){
28428         if (Ext.isFunction(renderer)) {
28429             return renderer;
28430         }
28431         return this.statics().Renderer.Html;
28432     },
28433
28434     /**
28435      * Automatically refreshes the content over a specified period.
28436      * @param {Number} interval The interval to refresh in ms.
28437      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
28438      */
28439     startAutoRefresh: function(interval, options){
28440         var me = this;
28441         me.stopAutoRefresh();
28442         me.autoRefresh = setInterval(function(){
28443             me.load(options);
28444         }, interval);
28445     },
28446
28447     /**
28448      * Clears any auto refresh. See {@link #startAutoRefresh}.
28449      */
28450     stopAutoRefresh: function(){
28451         clearInterval(this.autoRefresh);
28452         delete this.autoRefresh;
28453     },
28454
28455     /**
28456      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
28457      * @return {Boolean} True if the loader is automatically refreshing
28458      */
28459     isAutoRefreshing: function(){
28460         return Ext.isDefined(this.autoRefresh);
28461     },
28462
28463     /**
28464      * Destroys the loader. Any active requests will be aborted.
28465      */
28466     destroy: function(){
28467         var me = this;
28468         me.stopAutoRefresh();
28469         delete me.target;
28470         me.abort();
28471         me.clearListeners();
28472     }
28473 });
28474
28475 /**
28476  * @class Ext.ComponentLoader
28477  * @extends Ext.ElementLoader
28478  *
28479  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
28480  * this class will not be instanced directly, rather a loader configuration will be passed to the
28481  * constructor of the {@link Ext.Component}.
28482  *
28483  * ## HTML Renderer
28484  * By default, the content loaded will be processed as raw html. The response text
28485  * from the request is taken and added to the component. This can be used in
28486  * conjunction with the {@link #scripts} option to execute any inline scripts in
28487  * the resulting content. Using this renderer has the same effect as passing the
28488  * {@link Ext.Component#html} configuration option.
28489  *
28490  * ## Data Renderer
28491  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
28492  * The content received from the response is passed to the {@link Ext.Component#update} method.
28493  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
28494  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
28495  * configuration in conjunction with a {@link Ext.Component#tpl}.
28496  *
28497  * ## Component Renderer
28498  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
28499  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
28500  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
28501  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
28502  * the {@link Ext.container.Container#items} configuration on a Container.
28503  *
28504  * ## Custom Renderer
28505  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
28506  *
28507  * ## Example Usage
28508  *     new Ext.Component({
28509  *         tpl: '{firstName} - {lastName}',
28510  *         loader: {
28511  *             url: 'myPage.php',
28512  *             renderer: 'data',
28513  *             params: {
28514  *                 userId: 1
28515  *             }
28516  *         }
28517  *     });
28518  */
28519 Ext.define('Ext.ComponentLoader', {
28520
28521     /* Begin Definitions */
28522
28523     extend: 'Ext.ElementLoader',
28524
28525     statics: {
28526         Renderer: {
28527             Data: function(loader, response, active){
28528                 var success = true;
28529                 try {
28530                     loader.getTarget().update(Ext.decode(response.responseText));
28531                 } catch (e) {
28532                     success = false;
28533                 }
28534                 return success;
28535             },
28536
28537             Component: function(loader, response, active){
28538                 var success = true,
28539                     target = loader.getTarget(),
28540                     items = [];
28541
28542
28543                 try {
28544                     items = Ext.decode(response.responseText);
28545                 } catch (e) {
28546                     success = false;
28547                 }
28548
28549                 if (success) {
28550                     if (active.removeAll) {
28551                         target.removeAll();
28552                     }
28553                     target.add(items);
28554                 }
28555                 return success;
28556             }
28557         }
28558     },
28559
28560     /* End Definitions */
28561
28562     /**
28563      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
28564      * If a string is passed it will be looked up via the id.
28565      */
28566     target: null,
28567
28568     /**
28569      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
28570      */
28571     loadMask: false,
28572
28573     /**
28574      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
28575      * {@link #renderer}.
28576      */
28577
28578     /**
28579      * @cfg {String/Function} renderer
28580
28581 The type of content that is to be loaded into, which can be one of 3 types:
28582
28583 + **html** : Loads raw html content, see {@link Ext.Component#html}
28584 + **data** : Loads raw html content, see {@link Ext.Component#data}
28585 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
28586
28587 Alternatively, you can pass a function which is called with the following parameters.
28588
28589 + loader - Loader instance
28590 + response - The server response
28591 + active - The active request
28592
28593 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
28594
28595     new Ext.Component({
28596         loader: {
28597             url: 'myPage.php',
28598             renderer: function(loader, response, active) {
28599                 var text = response.responseText;
28600                 loader.getTarget().update('The response is ' + text);
28601                 return true;
28602             }
28603         }
28604     });
28605      */
28606     renderer: 'html',
28607
28608     /**
28609      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
28610      * any active requests will be aborted.
28611      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
28612      * it will be looked up via its id.
28613      */
28614     setTarget: function(target){
28615         var me = this;
28616
28617         if (Ext.isString(target)) {
28618             target = Ext.getCmp(target);
28619         }
28620
28621         if (me.target && me.target != target) {
28622             me.abort();
28623         }
28624         me.target = target;
28625     },
28626
28627     // inherit docs
28628     removeMask: function(){
28629         this.target.setLoading(false);
28630     },
28631
28632     /**
28633      * Add the mask on the target
28634      * @private
28635      * @param {Boolean/Object} mask The mask configuration
28636      */
28637     addMask: function(mask){
28638         this.target.setLoading(mask);
28639     },
28640
28641     /**
28642      * Get the target of this loader.
28643      * @return {Ext.Component} target The target, null if none exists.
28644      */
28645
28646     setOptions: function(active, options){
28647         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
28648     },
28649
28650     /**
28651      * Gets the renderer to use
28652      * @private
28653      * @param {String/Function} renderer The renderer to use
28654      * @return {Function} A rendering function to use.
28655      */
28656     getRenderer: function(renderer){
28657         if (Ext.isFunction(renderer)) {
28658             return renderer;
28659         }
28660
28661         var renderers = this.statics().Renderer;
28662         switch (renderer) {
28663             case 'component':
28664                 return renderers.Component;
28665             case 'data':
28666                 return renderers.Data;
28667             default:
28668                 return Ext.ElementLoader.Renderer.Html;
28669         }
28670     }
28671 });
28672
28673 /**
28674  * @author Ed Spencer
28675  *
28676  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
28677  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
28678  * express like this:
28679  *
28680  *     Ext.define('User', {
28681  *         extend: 'Ext.data.Model',
28682  *         fields: ['id', 'name', 'email'],
28683  *
28684  *         hasMany: {model: 'Order', name: 'orders'}
28685  *     });
28686  *
28687  *     Ext.define('Order', {
28688  *         extend: 'Ext.data.Model',
28689  *         fields: ['id', 'user_id', 'status', 'price'],
28690  *
28691  *         belongsTo: 'User'
28692  *     });
28693  *
28694  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
28695  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
28696  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
28697  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
28698  *
28699  * **Further Reading**
28700  *
28701  *   - {@link Ext.data.HasManyAssociation hasMany associations}
28702  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
28703  *   - {@link Ext.data.Model using Models}
28704  *
28705  * # Self association models
28706  *
28707  * We can also have models that create parent/child associations between the same type. Below is an example, where
28708  * groups can be nested inside other groups:
28709  *
28710  *     // Server Data
28711  *     {
28712  *         "groups": {
28713  *             "id": 10,
28714  *             "parent_id": 100,
28715  *             "name": "Main Group",
28716  *             "parent_group": {
28717  *                 "id": 100,
28718  *                 "parent_id": null,
28719  *                 "name": "Parent Group"
28720  *             },
28721  *             "child_groups": [{
28722  *                 "id": 2,
28723  *                 "parent_id": 10,
28724  *                 "name": "Child Group 1"
28725  *             },{
28726  *                 "id": 3,
28727  *                 "parent_id": 10,
28728  *                 "name": "Child Group 2"
28729  *             },{
28730  *                 "id": 4,
28731  *                 "parent_id": 10,
28732  *                 "name": "Child Group 3"
28733  *             }]
28734  *         }
28735  *     }
28736  *
28737  *     // Client code
28738  *     Ext.define('Group', {
28739  *         extend: 'Ext.data.Model',
28740  *         fields: ['id', 'parent_id', 'name'],
28741  *         proxy: {
28742  *             type: 'ajax',
28743  *             url: 'data.json',
28744  *             reader: {
28745  *                 type: 'json',
28746  *                 root: 'groups'
28747  *             }
28748  *         },
28749  *         associations: [{
28750  *             type: 'hasMany',
28751  *             model: 'Group',
28752  *             primaryKey: 'id',
28753  *             foreignKey: 'parent_id',
28754  *             autoLoad: true,
28755  *             associationKey: 'child_groups' // read child data from child_groups
28756  *         }, {
28757  *             type: 'belongsTo',
28758  *             model: 'Group',
28759  *             primaryKey: 'id',
28760  *             foreignKey: 'parent_id',
28761  *             associationKey: 'parent_group' // read parent data from parent_group
28762  *         }]
28763  *     });
28764  *
28765  *     Ext.onReady(function(){
28766  *
28767  *         Group.load(10, {
28768  *             success: function(group){
28769  *                 console.log(group.getGroup().get('name'));
28770  *
28771  *                 group.groups().each(function(rec){
28772  *                     console.log(rec.get('name'));
28773  *                 });
28774  *             }
28775  *         });
28776  *
28777  *     });
28778  *
28779  */
28780 Ext.define('Ext.data.Association', {
28781     /**
28782      * @cfg {String} ownerModel (required)
28783      * The string name of the model that owns the association.
28784      */
28785
28786     /**
28787      * @cfg {String} associatedModel (required)
28788      * The string name of the model that is being associated with.
28789      */
28790
28791     /**
28792      * @cfg {String} primaryKey
28793      * The name of the primary key on the associated model. In general this will be the
28794      * {@link Ext.data.Model#idProperty} of the Model.
28795      */
28796     primaryKey: 'id',
28797
28798     /**
28799      * @cfg {Ext.data.reader.Reader} reader
28800      * A special reader to read associated data
28801      */
28802     
28803     /**
28804      * @cfg {String} associationKey
28805      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
28806      */
28807
28808     defaultReaderType: 'json',
28809
28810     statics: {
28811         create: function(association){
28812             if (!association.isAssociation) {
28813                 if (Ext.isString(association)) {
28814                     association = {
28815                         type: association
28816                     };
28817                 }
28818
28819                 switch (association.type) {
28820                     case 'belongsTo':
28821                         return Ext.create('Ext.data.BelongsToAssociation', association);
28822                     case 'hasMany':
28823                         return Ext.create('Ext.data.HasManyAssociation', association);
28824                     //TODO Add this back when it's fixed
28825 //                    case 'polymorphic':
28826 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
28827                     default:
28828                 }
28829             }
28830             return association;
28831         }
28832     },
28833
28834     /**
28835      * Creates the Association object.
28836      * @param {Object} [config] Config object.
28837      */
28838     constructor: function(config) {
28839         Ext.apply(this, config);
28840
28841         var types           = Ext.ModelManager.types,
28842             ownerName       = config.ownerModel,
28843             associatedName  = config.associatedModel,
28844             ownerModel      = types[ownerName],
28845             associatedModel = types[associatedName],
28846             ownerProto;
28847
28848
28849         this.ownerModel = ownerModel;
28850         this.associatedModel = associatedModel;
28851
28852         /**
28853          * @property {String} ownerName
28854          * The name of the model that 'owns' the association
28855          */
28856
28857         /**
28858          * @property {String} associatedName
28859          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
28860          * 'Order')
28861          */
28862
28863         Ext.applyIf(this, {
28864             ownerName : ownerName,
28865             associatedName: associatedName
28866         });
28867     },
28868
28869     /**
28870      * Get a specialized reader for reading associated data
28871      * @return {Ext.data.reader.Reader} The reader, null if not supplied
28872      */
28873     getReader: function(){
28874         var me = this,
28875             reader = me.reader,
28876             model = me.associatedModel;
28877
28878         if (reader) {
28879             if (Ext.isString(reader)) {
28880                 reader = {
28881                     type: reader
28882                 };
28883             }
28884             if (reader.isReader) {
28885                 reader.setModel(model);
28886             } else {
28887                 Ext.applyIf(reader, {
28888                     model: model,
28889                     type : me.defaultReaderType
28890                 });
28891             }
28892             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
28893         }
28894         return me.reader || null;
28895     }
28896 });
28897
28898 /**
28899  * @author Ed Spencer
28900  * @class Ext.ModelManager
28901  * @extends Ext.AbstractManager
28902
28903 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
28904
28905 __Creating Model Instances__
28906
28907 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
28908 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
28909 this by using the Model type directly. The following 3 snippets are equivalent:
28910
28911     Ext.define('User', {
28912         extend: 'Ext.data.Model',
28913         fields: ['first', 'last']
28914     });
28915
28916     // method 1, create using Ext.create (recommended)
28917     Ext.create('User', {
28918         first: 'Ed',
28919         last: 'Spencer'
28920     });
28921
28922     // method 2, create through the manager (deprecated)
28923     Ext.ModelManager.create({
28924         first: 'Ed',
28925         last: 'Spencer'
28926     }, 'User');
28927
28928     // method 3, create on the type directly
28929     new User({
28930         first: 'Ed',
28931         last: 'Spencer'
28932     });
28933
28934 __Accessing Model Types__
28935
28936 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
28937 are normal classes, you can access the type directly. The following snippets are equivalent:
28938
28939     Ext.define('User', {
28940         extend: 'Ext.data.Model',
28941         fields: ['first', 'last']
28942     });
28943
28944     // method 1, access model type through the manager
28945     var UserType = Ext.ModelManager.getModel('User');
28946
28947     // method 2, reference the type directly
28948     var UserType = User;
28949
28950  * @markdown
28951  * @singleton
28952  */
28953 Ext.define('Ext.ModelManager', {
28954     extend: 'Ext.AbstractManager',
28955     alternateClassName: 'Ext.ModelMgr',
28956     requires: ['Ext.data.Association'],
28957
28958     singleton: true,
28959
28960     typeName: 'mtype',
28961
28962     /**
28963      * Private stack of associations that must be created once their associated model has been defined
28964      * @property {Ext.data.Association[]} associationStack
28965      */
28966     associationStack: [],
28967
28968     /**
28969      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
28970      * immediately, as are any addition plugins defined in the model config.
28971      * @private
28972      */
28973     registerType: function(name, config) {
28974         var proto = config.prototype,
28975             model;
28976         if (proto && proto.isModel) {
28977             // registering an already defined model
28978             model = config;
28979         } else {
28980             // passing in a configuration
28981             if (!config.extend) {
28982                 config.extend = 'Ext.data.Model';
28983             }
28984             model = Ext.define(name, config);
28985         }
28986         this.types[name] = model;
28987         return model;
28988     },
28989
28990     /**
28991      * @private
28992      * Private callback called whenever a model has just been defined. This sets up any associations
28993      * that were waiting for the given model to be defined
28994      * @param {Function} model The model that was just created
28995      */
28996     onModelDefined: function(model) {
28997         var stack  = this.associationStack,
28998             length = stack.length,
28999             create = [],
29000             association, i, created;
29001
29002         for (i = 0; i < length; i++) {
29003             association = stack[i];
29004
29005             if (association.associatedModel == model.modelName) {
29006                 create.push(association);
29007             }
29008         }
29009
29010         for (i = 0, length = create.length; i < length; i++) {
29011             created = create[i];
29012             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
29013             Ext.Array.remove(stack, created);
29014         }
29015     },
29016
29017     /**
29018      * Registers an association where one of the models defined doesn't exist yet.
29019      * The ModelManager will check when new models are registered if it can link them
29020      * together
29021      * @private
29022      * @param {Ext.data.Association} association The association
29023      */
29024     registerDeferredAssociation: function(association){
29025         this.associationStack.push(association);
29026     },
29027
29028     /**
29029      * Returns the {@link Ext.data.Model} for a given model name
29030      * @param {String/Object} id The id of the model or the model instance.
29031      * @return {Ext.data.Model} a model class.
29032      */
29033     getModel: function(id) {
29034         var model = id;
29035         if (typeof model == 'string') {
29036             model = this.types[model];
29037         }
29038         return model;
29039     },
29040
29041     /**
29042      * Creates a new instance of a Model using the given data.
29043      *
29044      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
29045      *
29046      *     Ext.create('User', {
29047      *         first: 'Ed',
29048      *         last: 'Spencer'
29049      *     });
29050      *
29051      * @param {Object} data Data to initialize the Model's fields with
29052      * @param {String} name The name of the model to create
29053      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
29054      */
29055     create: function(config, name, id) {
29056         var con = typeof name == 'function' ? name : this.types[name || config.name];
29057
29058         return new con(config, id);
29059     }
29060 }, function() {
29061
29062     /**
29063      * Old way for creating Model classes.  Instead use:
29064      *
29065      *     Ext.define("MyModel", {
29066      *         extend: "Ext.data.Model",
29067      *         fields: []
29068      *     });
29069      *
29070      * @param {String} name Name of the Model class.
29071      * @param {Object} config A configuration object for the Model you wish to create.
29072      * @return {Ext.data.Model} The newly registered Model
29073      * @member Ext
29074      * @deprecated 4.0.0 Use {@link Ext#define} instead.
29075      */
29076     Ext.regModel = function() {
29077         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
29078     };
29079 });
29080
29081 /**
29082  * @singleton
29083  *
29084  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
29085  *
29086  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
29087  *
29088  *     {
29089  *         ptype: 'gridviewdragdrop',
29090  *         dragText: 'Drag and drop to reorganize'
29091  *     }
29092  *
29093  * Or just use the ptype on its own:
29094  *
29095  *     'gridviewdragdrop'
29096  *
29097  * Alternatively you can instantiate the plugin with Ext.create:
29098  *
29099  *     Ext.create('Ext.view.plugin.AutoComplete', {
29100  *         ptype: 'gridviewdragdrop',
29101  *         dragText: 'Drag and drop to reorganize'
29102  *     })
29103  */
29104 Ext.define('Ext.PluginManager', {
29105     extend: 'Ext.AbstractManager',
29106     alternateClassName: 'Ext.PluginMgr',
29107     singleton: true,
29108     typeName: 'ptype',
29109
29110     /**
29111      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
29112      * instantiate.
29113      * @param {Object} config A configuration object for the Plugin you wish to create.
29114      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
29115      * contain a `ptype`. (Optional if the config contains a `ptype`).
29116      * @return {Ext.Component} The newly instantiated Plugin.
29117      */
29118     //create: function(plugin, defaultType) {
29119     //    if (plugin instanceof this) {
29120     //        return plugin;
29121     //    } else {
29122     //        var type, config = {};
29123     //
29124     //        if (Ext.isString(plugin)) {
29125     //            type = plugin;
29126     //        }
29127     //        else {
29128     //            type = plugin[this.typeName] || defaultType;
29129     //            config = plugin;
29130     //        }
29131     //
29132     //        return Ext.createByAlias('plugin.' + type, config);
29133     //    }
29134     //},
29135
29136     create : function(config, defaultType){
29137         if (config.init) {
29138             return config;
29139         } else {
29140             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
29141         }
29142
29143         // Prior system supported Singleton plugins.
29144         //var PluginCls = this.types[config.ptype || defaultType];
29145         //if (PluginCls.init) {
29146         //    return PluginCls;
29147         //} else {
29148         //    return new PluginCls(config);
29149         //}
29150     },
29151
29152     /**
29153      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
29154      * @param {String} type The type to search for
29155      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
29156      * truthy
29157      * @return {Ext.AbstractPlugin[]} All matching plugins
29158      */
29159     findByType: function(type, defaultsOnly) {
29160         var matches = [],
29161             types   = this.types;
29162
29163         for (var name in types) {
29164             if (!types.hasOwnProperty(name)) {
29165                 continue;
29166             }
29167             var item = types[name];
29168
29169             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
29170                 matches.push(item);
29171             }
29172         }
29173
29174         return matches;
29175     }
29176 }, function() {
29177     /**
29178      * Shorthand for {@link Ext.PluginManager#registerType}
29179      * @param {String} ptype The ptype mnemonic string by which the Plugin class
29180      * may be looked up.
29181      * @param {Function} cls The new Plugin class.
29182      * @member Ext
29183      * @method preg
29184      */
29185     Ext.preg = function() {
29186         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
29187     };
29188 });
29189
29190 /**
29191  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
29192  *
29193  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
29194  * arguments:
29195  *
29196  * # Single argument: String/Array
29197  *
29198  * The single argument may be either a String or an Array:
29199  *
29200  * - String:
29201  *
29202  *       var t = new Ext.Template("<div>Hello {0}.</div>");
29203  *       t.{@link #append}('some-element', ['foo']);
29204  *
29205  * - Array:
29206  *
29207  *   An Array will be combined with `join('')`.
29208  *
29209  *       var t = new Ext.Template([
29210  *           '<div name="{id}">',
29211  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
29212  *           '</div>',
29213  *       ]);
29214  *       t.{@link #compile}();
29215  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
29216  *
29217  * # Multiple arguments: String, Object, Array, ...
29218  *
29219  * Multiple arguments will be combined with `join('')`.
29220  *
29221  *     var t = new Ext.Template(
29222  *         '<div name="{id}">',
29223  *             '<span class="{cls}">{name} {value}</span>',
29224  *         '</div>',
29225  *         // a configuration object:
29226  *         {
29227  *             compiled: true,      // {@link #compile} immediately
29228  *         }
29229  *     );
29230  *
29231  * # Notes
29232  *
29233  * - For a list of available format functions, see {@link Ext.util.Format}.
29234  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
29235  */
29236 Ext.define('Ext.Template', {
29237
29238     /* Begin Definitions */
29239
29240     requires: ['Ext.DomHelper', 'Ext.util.Format'],
29241
29242     inheritableStatics: {
29243         /**
29244          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
29245          * @param {String/HTMLElement} el A DOM element or its id
29246          * @param {Object} config (optional) Config object
29247          * @return {Ext.Template} The created template
29248          * @static
29249          * @inheritable
29250          */
29251         from: function(el, config) {
29252             el = Ext.getDom(el);
29253             return new this(el.value || el.innerHTML, config || '');
29254         }
29255     },
29256
29257     /* End Definitions */
29258
29259     /**
29260      * Creates new template.
29261      * 
29262      * @param {String...} html List of strings to be concatenated into template.
29263      * Alternatively an array of strings can be given, but then no config object may be passed.
29264      * @param {Object} config (optional) Config object
29265      */
29266     constructor: function(html) {
29267         var me = this,
29268             args = arguments,
29269             buffer = [],
29270             i = 0,
29271             length = args.length,
29272             value;
29273
29274         me.initialConfig = {};
29275
29276         if (length > 1) {
29277             for (; i < length; i++) {
29278                 value = args[i];
29279                 if (typeof value == 'object') {
29280                     Ext.apply(me.initialConfig, value);
29281                     Ext.apply(me, value);
29282                 } else {
29283                     buffer.push(value);
29284                 }
29285             }
29286             html = buffer.join('');
29287         } else {
29288             if (Ext.isArray(html)) {
29289                 buffer.push(html.join(''));
29290             } else {
29291                 buffer.push(html);
29292             }
29293         }
29294
29295         // @private
29296         me.html = buffer.join('');
29297
29298         if (me.compiled) {
29299             me.compile();
29300         }
29301     },
29302
29303     isTemplate: true,
29304
29305     /**
29306      * @cfg {Boolean} compiled
29307      * True to immediately compile the template. Defaults to false.
29308      */
29309
29310     /**
29311      * @cfg {Boolean} disableFormats
29312      * True to disable format functions in the template. If the template doesn't contain
29313      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
29314      */
29315     disableFormats: false,
29316
29317     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
29318
29319     /**
29320      * Returns an HTML fragment of this template with the specified values applied.
29321      *
29322      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
29323      *
29324      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
29325      *     tpl.applyTemplate(['John', 25]);
29326      *
29327      * or an object:
29328      *
29329      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
29330      *     tpl.applyTemplate({name: 'John', age: 25});
29331      *
29332      * @return {String} The HTML fragment
29333      */
29334     applyTemplate: function(values) {
29335         var me = this,
29336             useFormat = me.disableFormats !== true,
29337             fm = Ext.util.Format,
29338             tpl = me;
29339
29340         if (me.compiled) {
29341             return me.compiled(values);
29342         }
29343         function fn(m, name, format, args) {
29344             if (format && useFormat) {
29345                 if (args) {
29346                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
29347                 } else {
29348                     args = [values[name]];
29349                 }
29350                 if (format.substr(0, 5) == "this.") {
29351                     return tpl[format.substr(5)].apply(tpl, args);
29352                 }
29353                 else {
29354                     return fm[format].apply(fm, args);
29355                 }
29356             }
29357             else {
29358                 return values[name] !== undefined ? values[name] : "";
29359             }
29360         }
29361         return me.html.replace(me.re, fn);
29362     },
29363
29364     /**
29365      * Sets the HTML used as the template and optionally compiles it.
29366      * @param {String} html
29367      * @param {Boolean} compile (optional) True to compile the template.
29368      * @return {Ext.Template} this
29369      */
29370     set: function(html, compile) {
29371         var me = this;
29372         me.html = html;
29373         me.compiled = null;
29374         return compile ? me.compile() : me;
29375     },
29376
29377     compileARe: /\\/g,
29378     compileBRe: /(\r\n|\n)/g,
29379     compileCRe: /'/g,
29380
29381     /**
29382      * Compiles the template into an internal function, eliminating the RegEx overhead.
29383      * @return {Ext.Template} this
29384      */
29385     compile: function() {
29386         var me = this,
29387             fm = Ext.util.Format,
29388             useFormat = me.disableFormats !== true,
29389             body, bodyReturn;
29390
29391         function fn(m, name, format, args) {
29392             if (format && useFormat) {
29393                 args = args ? ',' + args: "";
29394                 if (format.substr(0, 5) != "this.") {
29395                     format = "fm." + format + '(';
29396                 }
29397                 else {
29398                     format = 'this.' + format.substr(5) + '(';
29399                 }
29400             }
29401             else {
29402                 args = '';
29403                 format = "(values['" + name + "'] == undefined ? '' : ";
29404             }
29405             return "'," + format + "values['" + name + "']" + args + ") ,'";
29406         }
29407
29408         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
29409         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
29410         eval(body);
29411         return me;
29412     },
29413
29414     /**
29415      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
29416      *
29417      * @param {String/HTMLElement/Ext.Element} el The context element
29418      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29419      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29420      * @return {HTMLElement/Ext.Element} The new node or Element
29421      */
29422     insertFirst: function(el, values, returnElement) {
29423         return this.doInsert('afterBegin', el, values, returnElement);
29424     },
29425
29426     /**
29427      * Applies the supplied values to the template and inserts the new node(s) before el.
29428      *
29429      * @param {String/HTMLElement/Ext.Element} el The context element
29430      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29431      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29432      * @return {HTMLElement/Ext.Element} The new node or Element
29433      */
29434     insertBefore: function(el, values, returnElement) {
29435         return this.doInsert('beforeBegin', el, values, returnElement);
29436     },
29437
29438     /**
29439      * Applies the supplied values to the template and inserts the new node(s) after el.
29440      *
29441      * @param {String/HTMLElement/Ext.Element} el The context element
29442      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29443      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29444      * @return {HTMLElement/Ext.Element} The new node or Element
29445      */
29446     insertAfter: function(el, values, returnElement) {
29447         return this.doInsert('afterEnd', el, values, returnElement);
29448     },
29449
29450     /**
29451      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
29452      *
29453      * For example usage see {@link Ext.Template Ext.Template class docs}.
29454      *
29455      * @param {String/HTMLElement/Ext.Element} el The context element
29456      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29457      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
29458      * @return {HTMLElement/Ext.Element} The new node or Element
29459      */
29460     append: function(el, values, returnElement) {
29461         return this.doInsert('beforeEnd', el, values, returnElement);
29462     },
29463
29464     doInsert: function(where, el, values, returnEl) {
29465         el = Ext.getDom(el);
29466         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
29467         return returnEl ? Ext.get(newNode, true) : newNode;
29468     },
29469
29470     /**
29471      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
29472      *
29473      * @param {String/HTMLElement/Ext.Element} el The context element
29474      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29475      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29476      * @return {HTMLElement/Ext.Element} The new node or Element
29477      */
29478     overwrite: function(el, values, returnElement) {
29479         el = Ext.getDom(el);
29480         el.innerHTML = this.applyTemplate(values);
29481         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
29482     }
29483 }, function() {
29484
29485     /**
29486      * @method apply
29487      * @member Ext.Template
29488      * Alias for {@link #applyTemplate}.
29489      * @alias Ext.Template#applyTemplate
29490      */
29491     this.createAlias('apply', 'applyTemplate');
29492 });
29493
29494 /**
29495  * A template class that supports advanced functionality like:
29496  *
29497  * - Autofilling arrays using templates and sub-templates
29498  * - Conditional processing with basic comparison operators
29499  * - Basic math function support
29500  * - Execute arbitrary inline code with special built-in template variables
29501  * - Custom member functions
29502  * - 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
29503  *
29504  * XTemplate provides the templating mechanism built into:
29505  *
29506  * - {@link Ext.view.View}
29507  *
29508  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
29509  * demonstrate all of the supported features.
29510  *
29511  * # Sample Data
29512  *
29513  * This is the data object used for reference in each code example:
29514  *
29515  *     var data = {
29516  *         name: 'Tommy Maintz',
29517  *         title: 'Lead Developer',
29518  *         company: 'Sencha Inc.',
29519  *         email: 'tommy@sencha.com',
29520  *         address: '5 Cups Drive',
29521  *         city: 'Palo Alto',
29522  *         state: 'CA',
29523  *         zip: '44102',
29524  *         drinks: ['Coffee', 'Soda', 'Water'],
29525  *         kids: [
29526  *             {
29527  *                 name: 'Joshua',
29528  *                 age:3
29529  *             },
29530  *             {
29531  *                 name: 'Matthew',
29532  *                 age:2
29533  *             },
29534  *             {
29535  *                 name: 'Solomon',
29536  *                 age:0
29537  *             }
29538  *         ]
29539  *     };
29540  *
29541  * # Auto filling of arrays
29542  *
29543  * The **tpl** tag and the **for** operator are used to process the provided data object:
29544  *
29545  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
29546  *   tag for each item in the array.
29547  * - If for="." is specified, the data object provided is examined.
29548  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
29549  *
29550  * Examples:
29551  *
29552  *     <tpl for=".">...</tpl>       // loop through array at root node
29553  *     <tpl for="foo">...</tpl>     // loop through array at foo node
29554  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
29555  *
29556  * Using the sample data above:
29557  *
29558  *     var tpl = new Ext.XTemplate(
29559  *         '<p>Kids: ',
29560  *         '<tpl for=".">',       // process the data.kids node
29561  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
29562  *         '</tpl></p>'
29563  *     );
29564  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
29565  *
29566  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
29567  * object to populate the template:
29568  *
29569  *     var tpl = new Ext.XTemplate(
29570  *         '<p>Name: {name}</p>',
29571  *         '<p>Title: {title}</p>',
29572  *         '<p>Company: {company}</p>',
29573  *         '<p>Kids: ',
29574  *         '<tpl for="kids">',     // interrogate the kids property within the data
29575  *             '<p>{name}</p>',
29576  *         '</tpl></p>'
29577  *     );
29578  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
29579  *
29580  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
29581  * loop. This variable will represent the value of the array at the current index:
29582  *
29583  *     var tpl = new Ext.XTemplate(
29584  *         '<p>{name}\'s favorite beverages:</p>',
29585  *         '<tpl for="drinks">',
29586  *             '<div> - {.}</div>',
29587  *         '</tpl>'
29588  *     );
29589  *     tpl.overwrite(panel.body, data);
29590  *
29591  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
29592  * members via the **parent** object:
29593  *
29594  *     var tpl = new Ext.XTemplate(
29595  *         '<p>Name: {name}</p>',
29596  *         '<p>Kids: ',
29597  *         '<tpl for="kids">',
29598  *             '<tpl if="age &gt; 1">',
29599  *                 '<p>{name}</p>',
29600  *                 '<p>Dad: {parent.name}</p>',
29601  *             '</tpl>',
29602  *         '</tpl></p>'
29603  *     );
29604  *     tpl.overwrite(panel.body, data);
29605  *
29606  * # Conditional processing with basic comparison operators
29607  *
29608  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
29609  * specific parts of the template. Notes:
29610  *
29611  * - Double quotes must be encoded if used within the conditional
29612  * - There is no else operator -- if needed, two opposite if statements should be used.
29613  *
29614  * Examples:
29615  *
29616  *     <tpl if="age > 1 && age < 10">Child</tpl>
29617  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
29618  *     <tpl if="this.isGirl(name)">...</tpl>
29619  *     <tpl if="id==\'download\'">...</tpl>
29620  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
29621  *     // no good:
29622  *     <tpl if="name == "Tommy"">Hello</tpl>
29623  *     // encode " if it is part of the condition, e.g.
29624  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
29625  *
29626  * Using the sample data above:
29627  *
29628  *     var tpl = new Ext.XTemplate(
29629  *         '<p>Name: {name}</p>',
29630  *         '<p>Kids: ',
29631  *         '<tpl for="kids">',
29632  *             '<tpl if="age &gt; 1">',
29633  *                 '<p>{name}</p>',
29634  *             '</tpl>',
29635  *         '</tpl></p>'
29636  *     );
29637  *     tpl.overwrite(panel.body, data);
29638  *
29639  * # Basic math support
29640  *
29641  * The following basic math operators may be applied directly on numeric data values:
29642  *
29643  *     + - * /
29644  *
29645  * For example:
29646  *
29647  *     var tpl = new Ext.XTemplate(
29648  *         '<p>Name: {name}</p>',
29649  *         '<p>Kids: ',
29650  *         '<tpl for="kids">',
29651  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
29652  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
29653  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
29654  *                 '<p>Dad: {parent.name}</p>',
29655  *             '</tpl>',
29656  *         '</tpl></p>'
29657  *     );
29658  *     tpl.overwrite(panel.body, data);
29659  *
29660  * # Execute arbitrary inline code with special built-in template variables
29661  *
29662  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
29663  * variables available in that code:
29664  *
29665  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
29666  *   you can change what values is.
29667  * - **parent**: The scope (values) of the ancestor template.
29668  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
29669  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
29670  *
29671  * This example demonstrates basic row striping using an inline code block and the xindex variable:
29672  *
29673  *     var tpl = new Ext.XTemplate(
29674  *         '<p>Name: {name}</p>',
29675  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
29676  *         '<p>Kids: ',
29677  *         '<tpl for="kids">',
29678  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
29679  *             '{name}',
29680  *             '</div>',
29681  *         '</tpl></p>'
29682  *      );
29683  *     tpl.overwrite(panel.body, data);
29684  *
29685  * # Template member functions
29686  *
29687  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
29688  * more complex processing:
29689  *
29690  *     var tpl = new Ext.XTemplate(
29691  *         '<p>Name: {name}</p>',
29692  *         '<p>Kids: ',
29693  *         '<tpl for="kids">',
29694  *             '<tpl if="this.isGirl(name)">',
29695  *                 '<p>Girl: {name} - {age}</p>',
29696  *             '</tpl>',
29697  *              // use opposite if statement to simulate 'else' processing:
29698  *             '<tpl if="this.isGirl(name) == false">',
29699  *                 '<p>Boy: {name} - {age}</p>',
29700  *             '</tpl>',
29701  *             '<tpl if="this.isBaby(age)">',
29702  *                 '<p>{name} is a baby!</p>',
29703  *             '</tpl>',
29704  *         '</tpl></p>',
29705  *         {
29706  *             // XTemplate configuration:
29707  *             disableFormats: true,
29708  *             // member functions:
29709  *             isGirl: function(name){
29710  *                return name == 'Sara Grace';
29711  *             },
29712  *             isBaby: function(age){
29713  *                return age < 1;
29714  *             }
29715  *         }
29716  *     );
29717  *     tpl.overwrite(panel.body, data);
29718  */
29719 Ext.define('Ext.XTemplate', {
29720
29721     /* Begin Definitions */
29722
29723     extend: 'Ext.Template',
29724
29725     /* End Definitions */
29726
29727     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
29728     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
29729     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
29730     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
29731     constructor: function() {
29732         this.callParent(arguments);
29733
29734         var me = this,
29735             html = me.html,
29736             argsRe = me.argsRe,
29737             nameRe = me.nameRe,
29738             ifRe = me.ifRe,
29739             execRe = me.execRe,
29740             id = 0,
29741             tpls = [],
29742             VALUES = 'values',
29743             PARENT = 'parent',
29744             XINDEX = 'xindex',
29745             XCOUNT = 'xcount',
29746             RETURN = 'return ',
29747             WITHVALUES = 'with(values){ ',
29748             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
29749
29750         html = ['<tpl>', html, '</tpl>'].join('');
29751
29752         while ((m = html.match(argsRe))) {
29753             exp = null;
29754             fn = null;
29755             exec = null;
29756             matchName = m[0].match(nameRe);
29757             matchIf = m[0].match(ifRe);
29758             matchExec = m[0].match(execRe);
29759
29760             exp = matchIf ? matchIf[1] : null;
29761             if (exp) {
29762                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
29763             }
29764
29765             exp = matchExec ? matchExec[1] : null;
29766             if (exp) {
29767                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
29768             }
29769
29770             name = matchName ? matchName[1] : null;
29771             if (name) {
29772                 if (name === '.') {
29773                     name = VALUES;
29774                 } else if (name === '..') {
29775                     name = PARENT;
29776                 }
29777                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
29778             }
29779
29780             tpls.push({
29781                 id: id,
29782                 target: name,
29783                 exec: exec,
29784                 test: fn,
29785                 body: m[1] || ''
29786             });
29787
29788             html = html.replace(m[0], '{xtpl' + id + '}');
29789             id = id + 1;
29790         }
29791
29792         for (i = tpls.length - 1; i >= 0; --i) {
29793             me.compileTpl(tpls[i]);
29794         }
29795         me.master = tpls[tpls.length - 1];
29796         me.tpls = tpls;
29797     },
29798
29799     // @private
29800     applySubTemplate: function(id, values, parent, xindex, xcount) {
29801         var me = this, t = me.tpls[id];
29802         return t.compiled.call(me, values, parent, xindex, xcount);
29803     },
29804
29805     /**
29806      * @cfg {RegExp} codeRe
29807      * The regular expression used to match code variables. Default: matches {[expression]}.
29808      */
29809     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
29810
29811     /**
29812      * @cfg {Boolean} compiled
29813      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
29814      */
29815
29816     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
29817
29818     // @private
29819     compileTpl: function(tpl) {
29820         var fm = Ext.util.Format,
29821             me = this,
29822             useFormat = me.disableFormats !== true,
29823             body, bodyReturn, evaluatedFn;
29824
29825         function fn(m, name, format, args, math) {
29826             var v;
29827             // name is what is inside the {}
29828             // Name begins with xtpl, use a Sub Template
29829             if (name.substr(0, 4) == 'xtpl') {
29830                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
29831             }
29832             // name = "." - Just use the values object.
29833             if (name == '.') {
29834                 // filter to not include arrays/objects/nulls
29835                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
29836             }
29837
29838             // name = "#" - Use the xindex
29839             else if (name == '#') {
29840                 v = 'xindex';
29841             }
29842             else if (name.substr(0, 7) == "parent.") {
29843                 v = name;
29844             }
29845             // name has a . in it - Use object literal notation, starting from values
29846             else if (name.indexOf('.') != -1) {
29847                 v = "values." + name;
29848             }
29849
29850             // name is a property of values
29851             else {
29852                 v = "values['" + name + "']";
29853             }
29854             if (math) {
29855                 v = '(' + v + math + ')';
29856             }
29857             if (format && useFormat) {
29858                 args = args ? ',' + args : "";
29859                 if (format.substr(0, 5) != "this.") {
29860                     format = "fm." + format + '(';
29861                 }
29862                 else {
29863                     format = 'this.' + format.substr(5) + '(';
29864                 }
29865             }
29866             else {
29867                 args = '';
29868                 format = "(" + v + " === undefined ? '' : ";
29869             }
29870             return "'," + format + v + args + "),'";
29871         }
29872
29873         function codeFn(m, code) {
29874             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
29875             return "',(" + code.replace(me.compileARe, "'") + "),'";
29876         }
29877
29878         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
29879         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
29880         eval(body);
29881
29882         tpl.compiled = function(values, parent, xindex, xcount) {
29883             var vs,
29884                 length,
29885                 buffer,
29886                 i;
29887
29888             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
29889                 return '';
29890             }
29891
29892             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
29893             if (!vs) {
29894                return '';
29895             }
29896
29897             parent = tpl.target ? values : parent;
29898             if (tpl.target && Ext.isArray(vs)) {
29899                 buffer = [];
29900                 length = vs.length;
29901                 if (tpl.exec) {
29902                     for (i = 0; i < length; i++) {
29903                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29904                         tpl.exec.call(me, vs[i], parent, i + 1, length);
29905                     }
29906                 } else {
29907                     for (i = 0; i < length; i++) {
29908                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29909                     }
29910                 }
29911                 return buffer.join('');
29912             }
29913
29914             if (tpl.exec) {
29915                 tpl.exec.call(me, vs, parent, xindex, xcount);
29916             }
29917             return evaluatedFn.call(me, vs, parent, xindex, xcount);
29918         };
29919
29920         return this;
29921     },
29922
29923     // inherit docs from Ext.Template
29924     applyTemplate: function(values) {
29925         return this.master.compiled.call(this, values, {}, 1, 1);
29926     },
29927
29928     /**
29929      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
29930      * @return {Ext.XTemplate} this
29931      */
29932     compile: function() {
29933         return this;
29934     }
29935 }, function() {
29936     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
29937     this.createAlias('apply', 'applyTemplate');
29938 });
29939
29940 /**
29941  * @class Ext.app.Controller
29942  *
29943  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
29944  * views) and take some action. Here's how we might create a Controller to manage Users:
29945  *
29946  *     Ext.define('MyApp.controller.Users', {
29947  *         extend: 'Ext.app.Controller',
29948  *
29949  *         init: function() {
29950  *             console.log('Initialized Users! This happens before the Application launch function is called');
29951  *         }
29952  *     });
29953  *
29954  * The init function is a special method that is called when your application boots. It is called before the
29955  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
29956  * your Viewport is created.
29957  *
29958  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
29959  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
29960  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
29961  * our Users controller to tell us when the panel is rendered:
29962  *
29963  *     Ext.define('MyApp.controller.Users', {
29964  *         extend: 'Ext.app.Controller',
29965  *
29966  *         init: function() {
29967  *             this.control({
29968  *                 'viewport > panel': {
29969  *                     render: this.onPanelRendered
29970  *                 }
29971  *             });
29972  *         },
29973  *
29974  *         onPanelRendered: function() {
29975  *             console.log('The panel was rendered');
29976  *         }
29977  *     });
29978  *
29979  * We've updated the init function to use this.control to set up listeners on views in our application. The control
29980  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
29981  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
29982  * it allows us to pass a CSS-like selector that will find every matching component on the page.
29983  *
29984  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
29985  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
29986  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
29987  * onPanelRendered function is called.
29988  *
29989  * <u>Using refs</u>
29990  *
29991  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
29992  * make it really easy to get references to Views on your page. Let's look at an example of this now:
29993  *
29994  *     Ext.define('MyApp.controller.Users', {
29995  *         extend: 'Ext.app.Controller',
29996  *
29997  *         refs: [
29998  *             {
29999  *                 ref: 'list',
30000  *                 selector: 'grid'
30001  *             }
30002  *         ],
30003  *
30004  *         init: function() {
30005  *             this.control({
30006  *                 'button': {
30007  *                     click: this.refreshGrid
30008  *                 }
30009  *             });
30010  *         },
30011  *
30012  *         refreshGrid: function() {
30013  *             this.getList().store.load();
30014  *         }
30015  *     });
30016  *
30017  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
30018  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
30019  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30020  * assigns it to the reference 'list'.
30021  *
30022  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30023  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
30024  * was capitalized and prepended with get to go from 'list' to 'getList'.
30025  *
30026  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30027  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
30028  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30029  * match a single View in your application (in the case above our selector will match any grid on the page).
30030  *
30031  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30032  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
30033  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30034  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30035  *
30036  * You can create any number of refs and control any number of components this way, simply adding more functions to
30037  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
30038  * examples/app/feed-viewer folder in the SDK download.
30039  *
30040  * <u>Generated getter methods</u>
30041  *
30042  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
30043  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30044  *
30045  *     Ext.define('MyApp.controller.Users', {
30046  *         extend: 'Ext.app.Controller',
30047  *
30048  *         models: ['User'],
30049  *         stores: ['AllUsers', 'AdminUsers'],
30050  *
30051  *         init: function() {
30052  *             var User = this.getUserModel(),
30053  *                 allUsers = this.getAllUsersStore();
30054  *
30055  *             var ed = new User({name: 'Ed'});
30056  *             allUsers.add(ed);
30057  *         }
30058  *     });
30059  *
30060  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30061  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
30062  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30063  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
30064  * functionality.
30065  *
30066  * <u>Further Reading</u>
30067  *
30068  * For more information about writing Ext JS 4 applications, please see the
30069  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
30070  *
30071  * @docauthor Ed Spencer
30072  */
30073 Ext.define('Ext.app.Controller', {
30074
30075     mixins: {
30076         observable: 'Ext.util.Observable'
30077     },
30078
30079     /**
30080      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30081      */
30082     
30083     /**
30084      * @cfg {String[]} models
30085      * Array of models to require from AppName.model namespace. For example:
30086      * 
30087      *     Ext.define("MyApp.controller.Foo", {
30088      *         extend: "Ext.app.Controller",
30089      *         models: ['User', 'Vehicle']
30090      *     });
30091      * 
30092      * This is equivalent of:
30093      * 
30094      *     Ext.define("MyApp.controller.Foo", {
30095      *         extend: "Ext.app.Controller",
30096      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
30097      *     });
30098      * 
30099      */
30100
30101     /**
30102      * @cfg {String[]} views
30103      * Array of views to require from AppName.view namespace. For example:
30104      * 
30105      *     Ext.define("MyApp.controller.Foo", {
30106      *         extend: "Ext.app.Controller",
30107      *         views: ['List', 'Detail']
30108      *     });
30109      * 
30110      * This is equivalent of:
30111      * 
30112      *     Ext.define("MyApp.controller.Foo", {
30113      *         extend: "Ext.app.Controller",
30114      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
30115      *     });
30116      * 
30117      */
30118
30119     /**
30120      * @cfg {String[]} stores
30121      * Array of stores to require from AppName.store namespace. For example:
30122      * 
30123      *     Ext.define("MyApp.controller.Foo", {
30124      *         extend: "Ext.app.Controller",
30125      *         stores: ['Users', 'Vehicles']
30126      *     });
30127      * 
30128      * This is equivalent of:
30129      * 
30130      *     Ext.define("MyApp.controller.Foo", {
30131      *         extend: "Ext.app.Controller",
30132      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
30133      *     });
30134      * 
30135      */
30136
30137     onClassExtended: function(cls, data) {
30138         var className = Ext.getClassName(cls),
30139             match = className.match(/^(.*)\.controller\./);
30140
30141         if (match !== null) {
30142             var namespace = Ext.Loader.getPrefix(className) || match[1],
30143                 onBeforeClassCreated = data.onBeforeClassCreated,
30144                 requires = [],
30145                 modules = ['model', 'view', 'store'],
30146                 prefix;
30147
30148             data.onBeforeClassCreated = function(cls, data) {
30149                 var i, ln, module,
30150                     items, j, subLn, item;
30151
30152                 for (i = 0,ln = modules.length; i < ln; i++) {
30153                     module = modules[i];
30154
30155                     items = Ext.Array.from(data[module + 's']);
30156
30157                     for (j = 0,subLn = items.length; j < subLn; j++) {
30158                         item = items[j];
30159
30160                         prefix = Ext.Loader.getPrefix(item);
30161
30162                         if (prefix === '' || prefix === item) {
30163                             requires.push(namespace + '.' + module + '.' + item);
30164                         }
30165                         else {
30166                             requires.push(item);
30167                         }
30168                     }
30169                 }
30170
30171                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30172             };
30173         }
30174     },
30175
30176     /**
30177      * Creates new Controller.
30178      * @param {Object} config (optional) Config object.
30179      */
30180     constructor: function(config) {
30181         this.mixins.observable.constructor.call(this, config);
30182
30183         Ext.apply(this, config || {});
30184
30185         this.createGetters('model', this.models);
30186         this.createGetters('store', this.stores);
30187         this.createGetters('view', this.views);
30188
30189         if (this.refs) {
30190             this.ref(this.refs);
30191         }
30192     },
30193
30194     /**
30195      * A template method that is called when your application boots. It is called before the
30196      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30197      * your Viewport is created.
30198      * 
30199      * @param {Ext.app.Application} application
30200      * @template
30201      */
30202     init: function(application) {},
30203
30204     /**
30205      * A template method like {@link #init}, but called after the viewport is created.
30206      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
30207      * 
30208      * @param {Ext.app.Application} application
30209      * @template
30210      */
30211     onLaunch: function(application) {},
30212
30213     createGetters: function(type, refs) {
30214         type = Ext.String.capitalize(type);
30215         Ext.Array.each(refs, function(ref) {
30216             var fn = 'get',
30217                 parts = ref.split('.');
30218
30219             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30220             Ext.Array.each(parts, function(part) {
30221                 fn += Ext.String.capitalize(part);
30222             });
30223             fn += type;
30224
30225             if (!this[fn]) {
30226                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30227             }
30228             // Execute it right away
30229             this[fn](ref);
30230         },
30231         this);
30232     },
30233
30234     ref: function(refs) {
30235         var me = this;
30236         refs = Ext.Array.from(refs);
30237         Ext.Array.each(refs, function(info) {
30238             var ref = info.ref,
30239                 fn = 'get' + Ext.String.capitalize(ref);
30240             if (!me[fn]) {
30241                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30242             }
30243         });
30244     },
30245
30246     getRef: function(ref, info, config) {
30247         this.refCache = this.refCache || {};
30248         info = info || {};
30249         config = config || {};
30250
30251         Ext.apply(info, config);
30252
30253         if (info.forceCreate) {
30254             return Ext.ComponentManager.create(info, 'component');
30255         }
30256
30257         var me = this,
30258             selector = info.selector,
30259             cached = me.refCache[ref];
30260
30261         if (!cached) {
30262             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30263             if (!cached && info.autoCreate) {
30264                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30265             }
30266             if (cached) {
30267                 cached.on('beforedestroy', function() {
30268                     me.refCache[ref] = null;
30269                 });
30270             }
30271         }
30272
30273         return cached;
30274     },
30275
30276     /**
30277      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
30278      * object containing component paths mapped to a hash of listener functions.
30279      *
30280      * In the following example the `updateUser` function is mapped to to the `click`
30281      * event on a button component, which is a child of the `useredit` component.
30282      *
30283      *     Ext.define('AM.controller.Users', {
30284      *         init: function() {
30285      *             this.control({
30286      *                 'useredit button[action=save]': {
30287      *                     click: this.updateUser
30288      *                 }
30289      *             });
30290      *         },
30291      *
30292      *         updateUser: function(button) {
30293      *             console.log('clicked the Save button');
30294      *         }
30295      *     });
30296      *
30297      * See {@link Ext.ComponentQuery} for more information on component selectors.
30298      *
30299      * @param {String/Object} selectors If a String, the second argument is used as the
30300      * listeners, otherwise an object of selectors -> listeners is assumed
30301      * @param {Object} listeners
30302      */
30303     control: function(selectors, listeners) {
30304         this.application.control(selectors, listeners, this);
30305     },
30306
30307     /**
30308      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
30309      * When controller doesn't exist yet, it's created.
30310      * @param {String} name
30311      * @return {Ext.app.Controller} a controller instance.
30312      */
30313     getController: function(name) {
30314         return this.application.getController(name);
30315     },
30316
30317     /**
30318      * Returns instance of a {@link Ext.data.Store Store} with the given name.
30319      * When store doesn't exist yet, it's created.
30320      * @param {String} name
30321      * @return {Ext.data.Store} a store instance.
30322      */
30323     getStore: function(name) {
30324         return this.application.getStore(name);
30325     },
30326
30327     /**
30328      * Returns a {@link Ext.data.Model Model} class with the given name.
30329      * A shorthand for using {@link Ext.ModelManager#getModel}.
30330      * @param {String} name
30331      * @return {Ext.data.Model} a model class.
30332      */
30333     getModel: function(model) {
30334         return this.application.getModel(model);
30335     },
30336
30337     /**
30338      * Returns a View class with the given name.  To create an instance of the view,
30339      * you can use it like it's used by Application to create the Viewport:
30340      * 
30341      *     this.getView('Viewport').create();
30342      * 
30343      * @param {String} name
30344      * @return {Ext.Base} a view class.
30345      */
30346     getView: function(view) {
30347         return this.application.getView(view);
30348     }
30349 });
30350
30351 /**
30352  * @author Don Griffin
30353  *
30354  * This class is a base for all id generators. It also provides lookup of id generators by
30355  * their id.
30356  * 
30357  * Generally, id generators are used to generate a primary key for new model instances. There
30358  * are different approaches to solving this problem, so this mechanism has both simple use
30359  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
30360  * using the {@link Ext.data.Model#idgen} property.
30361  *
30362  * # Identity, Type and Shared IdGenerators
30363  *
30364  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
30365  * This is done by giving IdGenerator instances an id property by which they can be looked
30366  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
30367  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
30368  * assign them the same id:
30369  *
30370  *     Ext.define('MyApp.data.MyModelA', {
30371  *         extend: 'Ext.data.Model',
30372  *         idgen: {
30373  *             type: 'sequential',
30374  *             id: 'foo'
30375  *         }
30376  *     });
30377  *
30378  *     Ext.define('MyApp.data.MyModelB', {
30379  *         extend: 'Ext.data.Model',
30380  *         idgen: {
30381  *             type: 'sequential',
30382  *             id: 'foo'
30383  *         }
30384  *     });
30385  *
30386  * To make this as simple as possible for generator types that are shared by many (or all)
30387  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
30388  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
30389  * to its type ('uuid'). In other words, the following Models share the same generator:
30390  *
30391  *     Ext.define('MyApp.data.MyModelX', {
30392  *         extend: 'Ext.data.Model',
30393  *         idgen: 'uuid'
30394  *     });
30395  *
30396  *     Ext.define('MyApp.data.MyModelY', {
30397  *         extend: 'Ext.data.Model',
30398  *         idgen: 'uuid'
30399  *     });
30400  *
30401  * This can be overridden (by specifying the id explicitly), but there is no particularly
30402  * good reason to do so for this generator type.
30403  *
30404  * # Creating Custom Generators
30405  * 
30406  * An id generator should derive from this class and implement the {@link #generate} method.
30407  * The constructor will apply config properties on new instances, so a constructor is often
30408  * not necessary.
30409  *
30410  * To register an id generator type, a derived class should provide an `alias` like so:
30411  *
30412  *     Ext.define('MyApp.data.CustomIdGenerator', {
30413  *         extend: 'Ext.data.IdGenerator',
30414  *         alias: 'idgen.custom',
30415  *
30416  *         configProp: 42, // some config property w/default value
30417  *
30418  *         generate: function () {
30419  *             return ... // a new id
30420  *         }
30421  *     });
30422  *
30423  * Using the custom id generator is then straightforward:
30424  *
30425  *     Ext.define('MyApp.data.MyModel', {
30426  *         extend: 'Ext.data.Model',
30427  *         idgen: 'custom'
30428  *     });
30429  *     // or...
30430  *
30431  *     Ext.define('MyApp.data.MyModel', {
30432  *         extend: 'Ext.data.Model',
30433  *         idgen: {
30434  *             type: 'custom',
30435  *             configProp: value
30436  *         }
30437  *     });
30438  *
30439  * It is not recommended to mix shared generators with generator configuration. This leads
30440  * to unpredictable results unless all configurations match (which is also redundant). In
30441  * such cases, a custom generator with a default id is the best approach.
30442  *
30443  *     Ext.define('MyApp.data.CustomIdGenerator', {
30444  *         extend: 'Ext.data.SequentialIdGenerator',
30445  *         alias: 'idgen.custom',
30446  *
30447  *         id: 'custom', // shared by default
30448  *
30449  *         prefix: 'ID_',
30450  *         seed: 1000
30451  *     });
30452  *
30453  *     Ext.define('MyApp.data.MyModelX', {
30454  *         extend: 'Ext.data.Model',
30455  *         idgen: 'custom'
30456  *     });
30457  *
30458  *     Ext.define('MyApp.data.MyModelY', {
30459  *         extend: 'Ext.data.Model',
30460  *         idgen: 'custom'
30461  *     });
30462  *
30463  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
30464  *
30465  */
30466 Ext.define('Ext.data.IdGenerator', {
30467
30468     isGenerator: true,
30469
30470     /**
30471      * Initializes a new instance.
30472      * @param {Object} config (optional) Configuration object to be applied to the new instance.
30473      */
30474     constructor: function(config) {
30475         var me = this;
30476
30477         Ext.apply(me, config);
30478
30479         if (me.id) {
30480             Ext.data.IdGenerator.all[me.id] = me;
30481         }
30482     },
30483
30484     /**
30485      * @cfg {String} id
30486      * The id by which to register a new instance. This instance can be found using the
30487      * {@link Ext.data.IdGenerator#get} static method.
30488      */
30489
30490     getRecId: function (rec) {
30491         return rec.modelName + '-' + rec.internalId;
30492     },
30493
30494     /**
30495      * Generates and returns the next id. This method must be implemented by the derived
30496      * class.
30497      *
30498      * @return {String} The next id.
30499      * @method generate
30500      * @abstract
30501      */
30502
30503     statics: {
30504         /**
30505          * @property {Object} all
30506          * This object is keyed by id to lookup instances.
30507          * @private
30508          * @static
30509          */
30510         all: {},
30511
30512         /**
30513          * Returns the IdGenerator given its config description.
30514          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
30515          * simply returned. If this is a string, it is first used as an id for lookup and
30516          * then, if there is no match, as a type to create a new instance. This parameter
30517          * can also be a config object that contains a `type` property (among others) that
30518          * are used to create and configure the instance.
30519          * @static
30520          */
30521         get: function (config) {
30522             var generator,
30523                 id,
30524                 type;
30525
30526             if (typeof config == 'string') {
30527                 id = type = config;
30528                 config = null;
30529             } else if (config.isGenerator) {
30530                 return config;
30531             } else {
30532                 id = config.id || config.type;
30533                 type = config.type;
30534             }
30535
30536             generator = this.all[id];
30537             if (!generator) {
30538                 generator = Ext.create('idgen.' + type, config);
30539             }
30540
30541             return generator;
30542         }
30543     }
30544 });
30545
30546 /**
30547  * @class Ext.data.SortTypes
30548  * This class defines a series of static methods that are used on a
30549  * {@link Ext.data.Field} for performing sorting. The methods cast the 
30550  * underlying values into a data type that is appropriate for sorting on
30551  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
30552  * the sortType will be set to a sane default if the sortType is not 
30553  * explicitly defined on the field. The sortType will make any necessary
30554  * modifications to the value and return it.
30555  * <ul>
30556  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
30557  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
30558  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
30559  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
30560  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
30561  * <li><b>asInt</b> - Converts the value to an integer number</li>
30562  * </ul>
30563  * <p>
30564  * It is also possible to create a custom sortType that can be used throughout
30565  * an application.
30566  * <pre><code>
30567 Ext.apply(Ext.data.SortTypes, {
30568     asPerson: function(person){
30569         // expects an object with a first and last name property
30570         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
30571     }    
30572 });
30573
30574 Ext.define('Employee', {
30575     extend: 'Ext.data.Model',
30576     fields: [{
30577         name: 'person',
30578         sortType: 'asPerson'
30579     }, {
30580         name: 'salary',
30581         type: 'float' // sortType set to asFloat
30582     }]
30583 });
30584  * </code></pre>
30585  * </p>
30586  * @singleton
30587  * @docauthor Evan Trimboli <evan@sencha.com>
30588  */
30589 Ext.define('Ext.data.SortTypes', {
30590     
30591     singleton: true,
30592     
30593     /**
30594      * Default sort that does nothing
30595      * @param {Object} s The value being converted
30596      * @return {Object} The comparison value
30597      */
30598     none : function(s) {
30599         return s;
30600     },
30601
30602     /**
30603      * The regular expression used to strip tags
30604      * @type {RegExp}
30605      * @property
30606      */
30607     stripTagsRE : /<\/?[^>]+>/gi,
30608
30609     /**
30610      * Strips all HTML tags to sort on text only
30611      * @param {Object} s The value being converted
30612      * @return {String} The comparison value
30613      */
30614     asText : function(s) {
30615         return String(s).replace(this.stripTagsRE, "");
30616     },
30617
30618     /**
30619      * Strips all HTML tags to sort on text only - Case insensitive
30620      * @param {Object} s The value being converted
30621      * @return {String} The comparison value
30622      */
30623     asUCText : function(s) {
30624         return String(s).toUpperCase().replace(this.stripTagsRE, "");
30625     },
30626
30627     /**
30628      * Case insensitive string
30629      * @param {Object} s The value being converted
30630      * @return {String} The comparison value
30631      */
30632     asUCString : function(s) {
30633         return String(s).toUpperCase();
30634     },
30635
30636     /**
30637      * Date sorting
30638      * @param {Object} s The value being converted
30639      * @return {Number} The comparison value
30640      */
30641     asDate : function(s) {
30642         if(!s){
30643             return 0;
30644         }
30645         if(Ext.isDate(s)){
30646             return s.getTime();
30647         }
30648         return Date.parse(String(s));
30649     },
30650
30651     /**
30652      * Float sorting
30653      * @param {Object} s The value being converted
30654      * @return {Number} The comparison value
30655      */
30656     asFloat : function(s) {
30657         var val = parseFloat(String(s).replace(/,/g, ""));
30658         return isNaN(val) ? 0 : val;
30659     },
30660
30661     /**
30662      * Integer sorting
30663      * @param {Object} s The value being converted
30664      * @return {Number} The comparison value
30665      */
30666     asInt : function(s) {
30667         var val = parseInt(String(s).replace(/,/g, ""), 10);
30668         return isNaN(val) ? 0 : val;
30669     }
30670 });
30671 /**
30672  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
30673  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
30674  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
30675  * on their records. Example usage:
30676  *
30677  *     //set up a fictional MixedCollection containing a few people to filter on
30678  *     var allNames = new Ext.util.MixedCollection();
30679  *     allNames.addAll([
30680  *         {id: 1, name: 'Ed',    age: 25},
30681  *         {id: 2, name: 'Jamie', age: 37},
30682  *         {id: 3, name: 'Abe',   age: 32},
30683  *         {id: 4, name: 'Aaron', age: 26},
30684  *         {id: 5, name: 'David', age: 32}
30685  *     ]);
30686  *
30687  *     var ageFilter = new Ext.util.Filter({
30688  *         property: 'age',
30689  *         value   : 32
30690  *     });
30691  *
30692  *     var longNameFilter = new Ext.util.Filter({
30693  *         filterFn: function(item) {
30694  *             return item.name.length > 4;
30695  *         }
30696  *     });
30697  *
30698  *     //a new MixedCollection with the 3 names longer than 4 characters
30699  *     var longNames = allNames.filter(longNameFilter);
30700  *
30701  *     //a new MixedCollection with the 2 people of age 24:
30702  *     var youngFolk = allNames.filter(ageFilter);
30703  *
30704  */
30705 Ext.define('Ext.util.Filter', {
30706
30707     /* Begin Definitions */
30708
30709     /* End Definitions */
30710     /**
30711      * @cfg {String} property
30712      * The property to filter on. Required unless a {@link #filterFn} is passed
30713      */
30714     
30715     /**
30716      * @cfg {Function} filterFn
30717      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
30718      * true to accept each item or false to reject it
30719      */
30720     
30721     /**
30722      * @cfg {Boolean} anyMatch
30723      * True to allow any match - no regex start/end line anchors will be added.
30724      */
30725     anyMatch: false,
30726     
30727     /**
30728      * @cfg {Boolean} exactMatch
30729      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
30730      */
30731     exactMatch: false,
30732     
30733     /**
30734      * @cfg {Boolean} caseSensitive
30735      * True to make the regex case sensitive (adds 'i' switch to regex).
30736      */
30737     caseSensitive: false,
30738     
30739     /**
30740      * @cfg {String} root
30741      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
30742      * make the filter pull the {@link #property} out of the data object of each item
30743      */
30744
30745     /**
30746      * Creates new Filter.
30747      * @param {Object} [config] Config object
30748      */
30749     constructor: function(config) {
30750         var me = this;
30751         Ext.apply(me, config);
30752         
30753         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
30754         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
30755         me.filter = me.filter || me.filterFn;
30756         
30757         if (me.filter === undefined) {
30758             if (me.property === undefined || me.value === undefined) {
30759                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
30760                 // Model has been updated to allow string ids
30761                 
30762                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
30763             } else {
30764                 me.filter = me.createFilterFn();
30765             }
30766             
30767             me.filterFn = me.filter;
30768         }
30769     },
30770     
30771     /**
30772      * @private
30773      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
30774      */
30775     createFilterFn: function() {
30776         var me       = this,
30777             matcher  = me.createValueMatcher(),
30778             property = me.property;
30779         
30780         return function(item) {
30781             var value = me.getRoot.call(me, item)[property];
30782             return matcher === null ? value === null : matcher.test(value);
30783         };
30784     },
30785     
30786     /**
30787      * @private
30788      * Returns the root property of the given item, based on the configured {@link #root} property
30789      * @param {Object} item The item
30790      * @return {Object} The root property of the object
30791      */
30792     getRoot: function(item) {
30793         var root = this.root;
30794         return root === undefined ? item : item[root];
30795     },
30796     
30797     /**
30798      * @private
30799      * Returns a regular expression based on the given value and matching options
30800      */
30801     createValueMatcher : function() {
30802         var me            = this,
30803             value         = me.value,
30804             anyMatch      = me.anyMatch,
30805             exactMatch    = me.exactMatch,
30806             caseSensitive = me.caseSensitive,
30807             escapeRe      = Ext.String.escapeRegex;
30808             
30809         if (value === null) {
30810             return value;
30811         }
30812         
30813         if (!value.exec) { // not a regex
30814             value = String(value);
30815
30816             if (anyMatch === true) {
30817                 value = escapeRe(value);
30818             } else {
30819                 value = '^' + escapeRe(value);
30820                 if (exactMatch === true) {
30821                     value += '$';
30822                 }
30823             }
30824             value = new RegExp(value, caseSensitive ? '' : 'i');
30825          }
30826          
30827          return value;
30828     }
30829 });
30830 /**
30831  * Represents a single sorter that can be applied to a Store. The sorter is used
30832  * to compare two values against each other for the purpose of ordering them. Ordering
30833  * is achieved by specifying either:
30834  *
30835  * - {@link #property A sorting property}
30836  * - {@link #sorterFn A sorting function}
30837  *
30838  * As a contrived example, we can specify a custom sorter that sorts by rank:
30839  *
30840  *     Ext.define('Person', {
30841  *         extend: 'Ext.data.Model',
30842  *         fields: ['name', 'rank']
30843  *     });
30844  *
30845  *     Ext.create('Ext.data.Store', {
30846  *         model: 'Person',
30847  *         proxy: 'memory',
30848  *         sorters: [{
30849  *             sorterFn: function(o1, o2){
30850  *                 var getRank = function(o){
30851  *                     var name = o.get('rank');
30852  *                     if (name === 'first') {
30853  *                         return 1;
30854  *                     } else if (name === 'second') {
30855  *                         return 2;
30856  *                     } else {
30857  *                         return 3;
30858  *                     }
30859  *                 },
30860  *                 rank1 = getRank(o1),
30861  *                 rank2 = getRank(o2);
30862  *
30863  *                 if (rank1 === rank2) {
30864  *                     return 0;
30865  *                 }
30866  *
30867  *                 return rank1 < rank2 ? -1 : 1;
30868  *             }
30869  *         }],
30870  *         data: [{
30871  *             name: 'Person1',
30872  *             rank: 'second'
30873  *         }, {
30874  *             name: 'Person2',
30875  *             rank: 'third'
30876  *         }, {
30877  *             name: 'Person3',
30878  *             rank: 'first'
30879  *         }]
30880  *     });
30881  */
30882 Ext.define('Ext.util.Sorter', {
30883
30884     /**
30885      * @cfg {String} property
30886      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
30887      * directly and compared for sorting using the built in comparison operators.
30888      */
30889     
30890     /**
30891      * @cfg {Function} sorterFn
30892      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
30893      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
30894      * function should return:
30895      *
30896      *   - -1 if o1 is "less than" o2
30897      *   - 0 if o1 is "equal" to o2
30898      *   - 1 if o1 is "greater than" o2
30899      */
30900     
30901     /**
30902      * @cfg {String} root
30903      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
30904      * make the filter pull the {@link #property} out of the data object of each item
30905      */
30906     
30907     /**
30908      * @cfg {Function} transform
30909      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
30910      * argument, the value.
30911      */
30912     
30913     /**
30914      * @cfg {String} direction
30915      * The direction to sort by.
30916      */
30917     direction: "ASC",
30918     
30919     constructor: function(config) {
30920         var me = this;
30921         
30922         Ext.apply(me, config);
30923         
30924         
30925         me.updateSortFunction();
30926     },
30927     
30928     /**
30929      * @private
30930      * Creates and returns a function which sorts an array by the given property and direction
30931      * @return {Function} A function which sorts by the property/direction combination provided
30932      */
30933     createSortFunction: function(sorterFn) {
30934         var me        = this,
30935             property  = me.property,
30936             direction = me.direction || "ASC",
30937             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
30938         
30939         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
30940         //-1 if object 2 is greater or 0 if they are equal
30941         return function(o1, o2) {
30942             return modifier * sorterFn.call(me, o1, o2);
30943         };
30944     },
30945     
30946     /**
30947      * @private
30948      * Basic default sorter function that just compares the defined property of each object
30949      */
30950     defaultSorterFn: function(o1, o2) {
30951         var me = this,
30952             transform = me.transform,
30953             v1 = me.getRoot(o1)[me.property],
30954             v2 = me.getRoot(o2)[me.property];
30955             
30956         if (transform) {
30957             v1 = transform(v1);
30958             v2 = transform(v2);
30959         }
30960
30961         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
30962     },
30963     
30964     /**
30965      * @private
30966      * Returns the root property of the given item, based on the configured {@link #root} property
30967      * @param {Object} item The item
30968      * @return {Object} The root property of the object
30969      */
30970     getRoot: function(item) {
30971         return this.root === undefined ? item : item[this.root];
30972     },
30973     
30974     /**
30975      * Set the sorting direction for this sorter.
30976      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
30977      */
30978     setDirection: function(direction) {
30979         var me = this;
30980         me.direction = direction;
30981         me.updateSortFunction();
30982     },
30983     
30984     /**
30985      * Toggles the sorting direction for this sorter.
30986      */
30987     toggle: function() {
30988         var me = this;
30989         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
30990         me.updateSortFunction();
30991     },
30992     
30993     /**
30994      * Update the sort function for this sorter.
30995      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
30996      * sorting function.
30997      */
30998     updateSortFunction: function(fn) {
30999         var me = this;
31000         fn = fn || me.sorterFn || me.defaultSorterFn;
31001         me.sort = me.createSortFunction(fn);
31002     }
31003 });
31004 /**
31005  * @author Ed Spencer
31006  *
31007  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
31008  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
31009  * Operation objects directly.
31010  *
31011  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
31012  */
31013 Ext.define('Ext.data.Operation', {
31014     /**
31015      * @cfg {Boolean} synchronous
31016      * True if this Operation is to be executed synchronously. This property is inspected by a
31017      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
31018      */
31019     synchronous: true,
31020
31021     /**
31022      * @cfg {String} action
31023      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
31024      */
31025     action: undefined,
31026
31027     /**
31028      * @cfg {Ext.util.Filter[]} filters
31029      * Optional array of filter objects. Only applies to 'read' actions.
31030      */
31031     filters: undefined,
31032
31033     /**
31034      * @cfg {Ext.util.Sorter[]} sorters
31035      * Optional array of sorter objects. Only applies to 'read' actions.
31036      */
31037     sorters: undefined,
31038
31039     /**
31040      * @cfg {Ext.util.Grouper} group
31041      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31042      */
31043     group: undefined,
31044
31045     /**
31046      * @cfg {Number} start
31047      * The start index (offset), used in paging when running a 'read' action.
31048      */
31049     start: undefined,
31050
31051     /**
31052      * @cfg {Number} limit
31053      * The number of records to load. Used on 'read' actions when paging is being used.
31054      */
31055     limit: undefined,
31056
31057     /**
31058      * @cfg {Ext.data.Batch} batch
31059      * The batch that this Operation is a part of.
31060      */
31061     batch: undefined,
31062
31063     /**
31064      * @cfg {Function} callback
31065      * Function to execute when operation completed.  Will be called with the following parameters:
31066      *
31067      * - records : Array of Ext.data.Model objects.
31068      * - operation : The Ext.data.Operation itself.
31069      * - success : True when operation completed successfully.
31070      */
31071     callback: undefined,
31072
31073     /**
31074      * @cfg {Object} scope
31075      * Scope for the {@link #callback} function.
31076      */
31077     scope: undefined,
31078
31079     /**
31080      * @property {Boolean} started
31081      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31082      * @private
31083      */
31084     started: false,
31085
31086     /**
31087      * @property {Boolean} running
31088      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31089      * @private
31090      */
31091     running: false,
31092
31093     /**
31094      * @property {Boolean} complete
31095      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31096      * @private
31097      */
31098     complete: false,
31099
31100     /**
31101      * @property {Boolean} success
31102      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31103      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31104      * {@link #wasSuccessful} to query success status.
31105      * @private
31106      */
31107     success: undefined,
31108
31109     /**
31110      * @property {Boolean} exception
31111      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31112      * @private
31113      */
31114     exception: false,
31115
31116     /**
31117      * @property {String/Object} error
31118      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31119      * @private
31120      */
31121     error: undefined,
31122
31123     /**
31124      * @property {RegExp} actionCommitRecordsRe
31125      * The RegExp used to categorize actions that require record commits.
31126      */
31127     actionCommitRecordsRe: /^(?:create|update)$/i,
31128
31129     /**
31130      * @property {RegExp} actionSkipSyncRe
31131      * The RegExp used to categorize actions that skip local record synchronization. This defaults
31132      * to match 'destroy'.
31133      */
31134     actionSkipSyncRe: /^destroy$/i,
31135
31136     /**
31137      * Creates new Operation object.
31138      * @param {Object} config (optional) Config object.
31139      */
31140     constructor: function(config) {
31141         Ext.apply(this, config || {});
31142     },
31143
31144     /**
31145      * This method is called to commit data to this instance's records given the records in
31146      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
31147      * those records (for 'create' and 'update' actions).
31148      *
31149      * If this {@link #action} is 'destroy', any server records are ignored and the
31150      * {@link Ext.data.Model#commit} method is not called.
31151      *
31152      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
31153      * the server.
31154      * @markdown
31155      */
31156     commitRecords: function (serverRecords) {
31157         var me = this,
31158             mc, index, clientRecords, serverRec, clientRec;
31159
31160         if (!me.actionSkipSyncRe.test(me.action)) {
31161             clientRecords = me.records;
31162
31163             if (clientRecords && clientRecords.length) {
31164                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
31165                 mc.addAll(clientRecords);
31166
31167                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
31168                     serverRec = serverRecords[index];
31169                     clientRec = mc.get(serverRec.getId());
31170
31171                     if (clientRec) {
31172                         clientRec.beginEdit();
31173                         clientRec.set(serverRec.data);
31174                         clientRec.endEdit(true);
31175                     }
31176                 }
31177
31178                 if (me.actionCommitRecordsRe.test(me.action)) {
31179                     for (index = clientRecords.length; index--; ) {
31180                         clientRecords[index].commit();
31181                     }
31182                 }
31183             }
31184         }
31185     },
31186
31187     /**
31188      * Marks the Operation as started.
31189      */
31190     setStarted: function() {
31191         this.started = true;
31192         this.running = true;
31193     },
31194
31195     /**
31196      * Marks the Operation as completed.
31197      */
31198     setCompleted: function() {
31199         this.complete = true;
31200         this.running  = false;
31201     },
31202
31203     /**
31204      * Marks the Operation as successful.
31205      */
31206     setSuccessful: function() {
31207         this.success = true;
31208     },
31209
31210     /**
31211      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31212      * @param {String/Object} error (optional) error string/object
31213      */
31214     setException: function(error) {
31215         this.exception = true;
31216         this.success = false;
31217         this.running = false;
31218         this.error = error;
31219     },
31220
31221     /**
31222      * Returns true if this Operation encountered an exception (see also {@link #getError})
31223      * @return {Boolean} True if there was an exception
31224      */
31225     hasException: function() {
31226         return this.exception === true;
31227     },
31228
31229     /**
31230      * Returns the error string or object that was set using {@link #setException}
31231      * @return {String/Object} The error object
31232      */
31233     getError: function() {
31234         return this.error;
31235     },
31236
31237     /**
31238      * Returns an array of Ext.data.Model instances as set by the Proxy.
31239      * @return {Ext.data.Model[]} Any loaded Records
31240      */
31241     getRecords: function() {
31242         var resultSet = this.getResultSet();
31243
31244         return (resultSet === undefined ? this.records : resultSet.records);
31245     },
31246
31247     /**
31248      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
31249      * instances as well as meta data such as number of instances fetched, number available etc
31250      * @return {Ext.data.ResultSet} The ResultSet object
31251      */
31252     getResultSet: function() {
31253         return this.resultSet;
31254     },
31255
31256     /**
31257      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
31258      * {@link #isRunning} to test if the Operation is currently running.
31259      * @return {Boolean} True if the Operation has started
31260      */
31261     isStarted: function() {
31262         return this.started === true;
31263     },
31264
31265     /**
31266      * Returns true if the Operation has been started but has not yet completed.
31267      * @return {Boolean} True if the Operation is currently running
31268      */
31269     isRunning: function() {
31270         return this.running === true;
31271     },
31272
31273     /**
31274      * Returns true if the Operation has been completed
31275      * @return {Boolean} True if the Operation is complete
31276      */
31277     isComplete: function() {
31278         return this.complete === true;
31279     },
31280
31281     /**
31282      * Returns true if the Operation has completed and was successful
31283      * @return {Boolean} True if successful
31284      */
31285     wasSuccessful: function() {
31286         return this.isComplete() && this.success === true;
31287     },
31288
31289     /**
31290      * @private
31291      * Associates this Operation with a Batch
31292      * @param {Ext.data.Batch} batch The batch
31293      */
31294     setBatch: function(batch) {
31295         this.batch = batch;
31296     },
31297
31298     /**
31299      * Checks whether this operation should cause writing to occur.
31300      * @return {Boolean} Whether the operation should cause a write to occur.
31301      */
31302     allowWrite: function() {
31303         return this.action != 'read';
31304     }
31305 });
31306 /**
31307  * @author Ed Spencer
31308  *
31309  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
31310  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
31311  */
31312 Ext.define('Ext.data.validations', {
31313     singleton: true,
31314     
31315     /**
31316      * @property {String} presenceMessage
31317      * The default error message used when a presence validation fails.
31318      */
31319     presenceMessage: 'must be present',
31320     
31321     /**
31322      * @property {String} lengthMessage
31323      * The default error message used when a length validation fails.
31324      */
31325     lengthMessage: 'is the wrong length',
31326     
31327     /**
31328      * @property {Boolean} formatMessage
31329      * The default error message used when a format validation fails.
31330      */
31331     formatMessage: 'is the wrong format',
31332     
31333     /**
31334      * @property {String} inclusionMessage
31335      * The default error message used when an inclusion validation fails.
31336      */
31337     inclusionMessage: 'is not included in the list of acceptable values',
31338     
31339     /**
31340      * @property {String} exclusionMessage
31341      * The default error message used when an exclusion validation fails.
31342      */
31343     exclusionMessage: 'is not an acceptable value',
31344     
31345     /**
31346      * @property {String} emailMessage
31347      * The default error message used when an email validation fails
31348      */
31349     emailMessage: 'is not a valid email address',
31350     
31351     /**
31352      * @property {RegExp} emailRe
31353      * The regular expression used to validate email addresses
31354      */
31355     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
31356     
31357     /**
31358      * Validates that the given value is present.
31359      * For example:
31360      *
31361      *     validations: [{type: 'presence', field: 'age'}]
31362      *
31363      * @param {Object} config Config object
31364      * @param {Object} value The value to validate
31365      * @return {Boolean} True if validation passed
31366      */
31367     presence: function(config, value) {
31368         if (value === undefined) {
31369             value = config;
31370         }
31371         
31372         //we need an additional check for zero here because zero is an acceptable form of present data
31373         return !!value || value === 0;
31374     },
31375     
31376     /**
31377      * Returns true if the given value is between the configured min and max values.
31378      * For example:
31379      *
31380      *     validations: [{type: 'length', field: 'name', min: 2}]
31381      *
31382      * @param {Object} config Config object
31383      * @param {String} value The value to validate
31384      * @return {Boolean} True if the value passes validation
31385      */
31386     length: function(config, value) {
31387         if (value === undefined || value === null) {
31388             return false;
31389         }
31390         
31391         var length = value.length,
31392             min    = config.min,
31393             max    = config.max;
31394         
31395         if ((min && length < min) || (max && length > max)) {
31396             return false;
31397         } else {
31398             return true;
31399         }
31400     },
31401     
31402     /**
31403      * Validates that an email string is in the correct format
31404      * @param {Object} config Config object
31405      * @param {String} email The email address
31406      * @return {Boolean} True if the value passes validation
31407      */
31408     email: function(config, email) {
31409         return Ext.data.validations.emailRe.test(email);
31410     },
31411     
31412     /**
31413      * Returns true if the given value passes validation against the configured `matcher` regex.
31414      * For example:
31415      *
31416      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
31417      *
31418      * @param {Object} config Config object
31419      * @param {String} value The value to validate
31420      * @return {Boolean} True if the value passes the format validation
31421      */
31422     format: function(config, value) {
31423         return !!(config.matcher && config.matcher.test(value));
31424     },
31425     
31426     /**
31427      * Validates that the given value is present in the configured `list`.
31428      * For example:
31429      *
31430      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
31431      *
31432      * @param {Object} config Config object
31433      * @param {String} value The value to validate
31434      * @return {Boolean} True if the value is present in the list
31435      */
31436     inclusion: function(config, value) {
31437         return config.list && Ext.Array.indexOf(config.list,value) != -1;
31438     },
31439     
31440     /**
31441      * Validates that the given value is present in the configured `list`.
31442      * For example:
31443      *
31444      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
31445      *
31446      * @param {Object} config Config object
31447      * @param {String} value The value to validate
31448      * @return {Boolean} True if the value is not present in the list
31449      */
31450     exclusion: function(config, value) {
31451         return config.list && Ext.Array.indexOf(config.list,value) == -1;
31452     }
31453 });
31454 /**
31455  * @author Ed Spencer
31456  *
31457  * Simple wrapper class that represents a set of records returned by a Proxy.
31458  */
31459 Ext.define('Ext.data.ResultSet', {
31460     /**
31461      * @cfg {Boolean} loaded
31462      * True if the records have already been loaded. This is only meaningful when dealing with
31463      * SQL-backed proxies.
31464      */
31465     loaded: true,
31466
31467     /**
31468      * @cfg {Number} count
31469      * The number of records in this ResultSet. Note that total may differ from this number.
31470      */
31471     count: 0,
31472
31473     /**
31474      * @cfg {Number} total
31475      * The total number of records reported by the data source. This ResultSet may form a subset of
31476      * those records (see {@link #count}).
31477      */
31478     total: 0,
31479
31480     /**
31481      * @cfg {Boolean} success
31482      * True if the ResultSet loaded successfully, false if any errors were encountered.
31483      */
31484     success: false,
31485
31486     /**
31487      * @cfg {Ext.data.Model[]} records (required)
31488      * The array of record instances.
31489      */
31490
31491     /**
31492      * Creates the resultSet
31493      * @param {Object} [config] Config object.
31494      */
31495     constructor: function(config) {
31496         Ext.apply(this, config);
31497
31498         /**
31499          * @property {Number} totalRecords
31500          * Copy of this.total.
31501          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
31502          */
31503         this.totalRecords = this.total;
31504
31505         if (config.count === undefined) {
31506             this.count = this.records.length;
31507         }
31508     }
31509 });
31510 /**
31511  * @author Ed Spencer
31512  *
31513  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
31514  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
31515  * the Operations.
31516  *
31517  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
31518  * the config options passed to the JsonWriter's constructor.
31519  *
31520  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
31521  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
31522  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
31523  */
31524 Ext.define('Ext.data.writer.Writer', {
31525     alias: 'writer.base',
31526     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
31527     
31528     /**
31529      * @cfg {Boolean} writeAllFields
31530      * True to write all fields from the record to the server. If set to false it will only send the fields that were
31531      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
31532      */
31533     writeAllFields: true,
31534     
31535     /**
31536      * @cfg {String} nameProperty
31537      * This property is used to read the key for each value that will be sent to the server. For example:
31538      *
31539      *     Ext.define('Person', {
31540      *         extend: 'Ext.data.Model',
31541      *         fields: [{
31542      *             name: 'first',
31543      *             mapping: 'firstName'
31544      *         }, {
31545      *             name: 'last',
31546      *             mapping: 'lastName'
31547      *         }, {
31548      *             name: 'age'
31549      *         }]
31550      *     });
31551      *     new Ext.data.writer.Writer({
31552      *         writeAllFields: true,
31553      *         nameProperty: 'mapping'
31554      *     });
31555      *
31556      *     // This will be sent to the server
31557      *     {
31558      *         firstName: 'first name value',
31559      *         lastName: 'last name value',
31560      *         age: 1
31561      *     }
31562      *
31563      * If the value is not present, the field name will always be used.
31564      */
31565     nameProperty: 'name',
31566
31567     /**
31568      * Creates new Writer.
31569      * @param {Object} [config] Config object.
31570      */
31571     constructor: function(config) {
31572         Ext.apply(this, config);
31573     },
31574
31575     /**
31576      * Prepares a Proxy's Ext.data.Request object
31577      * @param {Ext.data.Request} request The request object
31578      * @return {Ext.data.Request} The modified request object
31579      */
31580     write: function(request) {
31581         var operation = request.operation,
31582             records   = operation.records || [],
31583             len       = records.length,
31584             i         = 0,
31585             data      = [];
31586
31587         for (; i < len; i++) {
31588             data.push(this.getRecordData(records[i]));
31589         }
31590         return this.writeRecords(request, data);
31591     },
31592
31593     /**
31594      * Formats the data for each record before sending it to the server. This method should be overridden to format the
31595      * data in a way that differs from the default.
31596      * @param {Object} record The record that we are writing to the server.
31597      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
31598      * the data property on the record.
31599      */
31600     getRecordData: function(record) {
31601         var isPhantom = record.phantom === true,
31602             writeAll = this.writeAllFields || isPhantom,
31603             nameProperty = this.nameProperty,
31604             fields = record.fields,
31605             data = {},
31606             changes,
31607             name,
31608             field,
31609             key;
31610         
31611         if (writeAll) {
31612             fields.each(function(field){
31613                 if (field.persist) {
31614                     name = field[nameProperty] || field.name;
31615                     data[name] = record.get(field.name);
31616                 }
31617             });
31618         } else {
31619             // Only write the changes
31620             changes = record.getChanges();
31621             for (key in changes) {
31622                 if (changes.hasOwnProperty(key)) {
31623                     field = fields.get(key);
31624                     name = field[nameProperty] || field.name;
31625                     data[name] = changes[key];
31626                 }
31627             }
31628             if (!isPhantom) {
31629                 // always include the id for non phantoms
31630                 data[record.idProperty] = record.getId();
31631             }
31632         }
31633         return data;
31634     }
31635 });
31636
31637 /**
31638  * A mixin to add floating capability to a Component.
31639  */
31640 Ext.define('Ext.util.Floating', {
31641
31642     uses: ['Ext.Layer', 'Ext.window.Window'],
31643
31644     /**
31645      * @cfg {Boolean} focusOnToFront
31646      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
31647      * it is {@link #toFront brought to the front}.
31648      */
31649     focusOnToFront: true,
31650
31651     /**
31652      * @cfg {String/Boolean} shadow
31653      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
31654      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
31655      * shadow.
31656      */
31657     shadow: 'sides',
31658
31659     constructor: function(config) {
31660         var me = this;
31661         
31662         me.floating = true;
31663         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
31664             hideMode: me.hideMode,
31665             hidden: me.hidden,
31666             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
31667             shadowOffset: me.shadowOffset,
31668             constrain: false,
31669             shim: me.shim === false ? false : undefined
31670         }), me.el);
31671     },
31672
31673     onFloatRender: function() {
31674         var me = this;
31675         me.zIndexParent = me.getZIndexParent();
31676         me.setFloatParent(me.ownerCt);
31677         delete me.ownerCt;
31678
31679         if (me.zIndexParent) {
31680             me.zIndexParent.registerFloatingItem(me);
31681         } else {
31682             Ext.WindowManager.register(me);
31683         }
31684     },
31685
31686     setFloatParent: function(floatParent) {
31687         var me = this;
31688
31689         // Remove listeners from previous floatParent
31690         if (me.floatParent) {
31691             me.mun(me.floatParent, {
31692                 hide: me.onFloatParentHide,
31693                 show: me.onFloatParentShow,
31694                 scope: me
31695             });
31696         }
31697
31698         me.floatParent = floatParent;
31699
31700         // Floating Components as children of Containers must hide when their parent hides.
31701         if (floatParent) {
31702             me.mon(me.floatParent, {
31703                 hide: me.onFloatParentHide,
31704                 show: me.onFloatParentShow,
31705                 scope: me
31706             });
31707         }
31708
31709         // If a floating Component is configured to be constrained, but has no configured
31710         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
31711         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
31712             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
31713         }
31714     },
31715
31716     onFloatParentHide: function() {
31717         var me = this;
31718         
31719         if (me.hideOnParentHide !== false) {
31720             me.showOnParentShow = me.isVisible();
31721             me.hide();
31722         }
31723     },
31724
31725     onFloatParentShow: function() {
31726         if (this.showOnParentShow) {
31727             delete this.showOnParentShow;
31728             this.show();
31729         }
31730     },
31731
31732     /**
31733      * @private
31734      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
31735      *
31736      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
31737      *
31738      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
31739      * and the global Ext.WindowManager will be used.
31740      */
31741     getZIndexParent: function() {
31742         var p = this.ownerCt,
31743             c;
31744
31745         if (p) {
31746             while (p) {
31747                 c = p;
31748                 p = p.ownerCt;
31749             }
31750             if (c.floating) {
31751                 return c;
31752             }
31753         }
31754     },
31755
31756     // private
31757     // z-index is managed by the zIndexManager and may be overwritten at any time.
31758     // Returns the next z-index to be used.
31759     // If this is a Container, then it will have rebased any managed floating Components,
31760     // and so the next available z-index will be approximately 10000 above that.
31761     setZIndex: function(index) {
31762         var me = this;
31763         me.el.setZIndex(index);
31764
31765         // Next item goes 10 above;
31766         index += 10;
31767
31768         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
31769         // The returned value is a round number approximately 10000 above the last z-index used.
31770         if (me.floatingItems) {
31771             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
31772         }
31773         return index;
31774     },
31775
31776     /**
31777      * Moves this floating Component into a constrain region.
31778      *
31779      * By default, this Component is constrained to be within the container it was added to, or the element it was
31780      * rendered to.
31781      *
31782      * An alternative constraint may be passed.
31783      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
31784      * to be constrained. Defaults to the element into which this floating Component was rendered.
31785      */
31786     doConstrain: function(constrainTo) {
31787         var me = this,
31788             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
31789             xy;
31790
31791         if (vector) {
31792             xy = me.getPosition();
31793             xy[0] += vector[0];
31794             xy[1] += vector[1];
31795             me.setPosition(xy);
31796         }
31797     },
31798
31799
31800     /**
31801      * Gets the x/y offsets to constrain this float
31802      * @private
31803      * @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.
31804      * @return {Number[]} The x/y constraints
31805      */
31806     getConstrainVector: function(constrainTo){
31807         var me = this,
31808             el;
31809
31810         if (me.constrain || me.constrainHeader) {
31811             el = me.constrainHeader ? me.header.el : me.el;
31812             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
31813             return el.getConstrainVector(constrainTo);
31814         }
31815     },
31816
31817     /**
31818      * Aligns this floating Component to the specified element
31819      *
31820      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
31821      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
31822      * omponent instance. If a string id is passed, it will be used as an element id.
31823      * @param {String} [position="tl-bl?"] The position to align to (see {@link
31824      * Ext.Element#alignTo} for more details).
31825      * @param {Number[]} [offsets] Offset the positioning by [x, y]
31826      * @return {Ext.Component} this
31827      */
31828     alignTo: function(element, position, offsets) {
31829         if (element.isComponent) {
31830             element = element.getEl();
31831         }
31832         var xy = this.el.getAlignToXY(element, position, offsets);
31833         this.setPagePosition(xy);
31834         return this;
31835     },
31836
31837     /**
31838      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
31839      * Ext.ZIndexManager ZIndexManager}
31840      *
31841      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
31842      *
31843      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
31844      * @return {Ext.Component} this
31845      */
31846     toFront: function(preventFocus) {
31847         var me = this;
31848
31849         // Find the floating Component which provides the base for this Component's zIndexing.
31850         // That must move to front to then be able to rebase its zIndex stack and move this to the front
31851         if (me.zIndexParent) {
31852             me.zIndexParent.toFront(true);
31853         }
31854         if (me.zIndexManager.bringToFront(me)) {
31855             if (!Ext.isDefined(preventFocus)) {
31856                 preventFocus = !me.focusOnToFront;
31857             }
31858             if (!preventFocus) {
31859                 // Kick off a delayed focus request.
31860                 // If another floating Component is toFronted before the delay expires
31861                 // this will not receive focus.
31862                 me.focus(false, true);
31863             }
31864         }
31865         return me;
31866     },
31867
31868     /**
31869      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
31870      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
31871      *
31872      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
31873      *
31874      * This method also fires the {@link Ext.Component#activate activate} or
31875      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
31876      *
31877      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
31878      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
31879      */
31880     setActive: function(active, newActive) {
31881         var me = this;
31882         
31883         if (active) {
31884             if (me.el.shadow && !me.maximized) {
31885                 me.el.enableShadow(true);
31886             }
31887             me.fireEvent('activate', me);
31888         } else {
31889             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
31890             // can keep their shadows all the time
31891             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
31892                 me.el.disableShadow();
31893             }
31894             me.fireEvent('deactivate', me);
31895         }
31896     },
31897
31898     /**
31899      * Sends this Component to the back of (lower z-index than) any other visible windows
31900      * @return {Ext.Component} this
31901      */
31902     toBack: function() {
31903         this.zIndexManager.sendToBack(this);
31904         return this;
31905     },
31906
31907     /**
31908      * Center this Component in its container.
31909      * @return {Ext.Component} this
31910      */
31911     center: function() {
31912         var me = this,
31913             xy = me.el.getAlignToXY(me.container, 'c-c');
31914         me.setPagePosition(xy);
31915         return me;
31916     },
31917
31918     // private
31919     syncShadow : function(){
31920         if (this.floating) {
31921             this.el.sync(true);
31922         }
31923     },
31924
31925     // private
31926     fitContainer: function() {
31927         var parent = this.floatParent,
31928             container = parent ? parent.getTargetEl() : this.container,
31929             size = container.getViewSize(false);
31930
31931         this.setSize(size);
31932     }
31933 });
31934 /**
31935  * Base Layout class - extended by ComponentLayout and ContainerLayout
31936  */
31937 Ext.define('Ext.layout.Layout', {
31938
31939     /* Begin Definitions */
31940
31941     /* End Definitions */
31942
31943     isLayout: true,
31944     initialized: false,
31945
31946     statics: {
31947         create: function(layout, defaultType) {
31948             var type;
31949             if (layout instanceof Ext.layout.Layout) {
31950                 return Ext.createByAlias('layout.' + layout);
31951             } else {
31952                 if (!layout || typeof layout === 'string') {
31953                     type = layout || defaultType;
31954                     layout = {};                    
31955                 }
31956                 else {
31957                     type = layout.type || defaultType;
31958                 }
31959                 return Ext.createByAlias('layout.' + type, layout || {});
31960             }
31961         }
31962     },
31963
31964     constructor : function(config) {
31965         this.id = Ext.id(null, this.type + '-');
31966         Ext.apply(this, config);
31967     },
31968
31969     /**
31970      * @private
31971      */
31972     layout : function() {
31973         var me = this;
31974         me.layoutBusy = true;
31975         me.initLayout();
31976
31977         if (me.beforeLayout.apply(me, arguments) !== false) {
31978             me.layoutCancelled = false;
31979             me.onLayout.apply(me, arguments);
31980             me.childrenChanged = false;
31981             me.owner.needsLayout = false;
31982             me.layoutBusy = false;
31983             me.afterLayout.apply(me, arguments);
31984         }
31985         else {
31986             me.layoutCancelled = true;
31987         }
31988         me.layoutBusy = false;
31989         me.doOwnerCtLayouts();
31990     },
31991
31992     beforeLayout : function() {
31993         this.renderChildren();
31994         return true;
31995     },
31996
31997     renderChildren: function () {
31998         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
31999     },
32000
32001     /**
32002      * @private
32003      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
32004      * also determines if the items are in the proper place dom.
32005      */
32006     renderItems : function(items, target) {
32007         var me = this,
32008             ln = items.length,
32009             i = 0,
32010             item;
32011
32012         for (; i < ln; i++) {
32013             item = items[i];
32014             if (item && !item.rendered) {
32015                 me.renderItem(item, target, i);
32016             } else if (!me.isValidParent(item, target, i)) {
32017                 me.moveItem(item, target, i);
32018             } else {
32019                 // still need to configure the item, it may have moved in the container.
32020                 me.configureItem(item);
32021             }
32022         }
32023     },
32024
32025     // @private - Validates item is in the proper place in the dom.
32026     isValidParent : function(item, target, position) {
32027         var dom = item.el ? item.el.dom : Ext.getDom(item);
32028         if (dom && target && target.dom) {
32029             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
32030                 return false;
32031             }
32032             return (dom.parentNode == (target.dom || target));
32033         }
32034         return false;
32035     },
32036
32037     /**
32038      * @private
32039      * Renders the given Component into the target Element.
32040      * @param {Ext.Component} item The Component to render
32041      * @param {Ext.Element} target The target Element
32042      * @param {Number} position The position within the target to render the item to
32043      */
32044     renderItem : function(item, target, position) {
32045         var me = this;
32046         if (!item.rendered) {
32047             if (me.itemCls) {
32048                 item.addCls(me.itemCls);
32049             }
32050             if (me.owner.itemCls) {
32051                 item.addCls(me.owner.itemCls);
32052             }
32053             item.render(target, position);
32054             me.configureItem(item);
32055             me.childrenChanged = true;
32056         }
32057     },
32058
32059     /**
32060      * @private
32061      * Moved Component to the provided target instead.
32062      */
32063     moveItem : function(item, target, position) {
32064         // Make sure target is a dom element
32065         target = target.dom || target;
32066         if (typeof position == 'number') {
32067             position = target.childNodes[position];
32068         }
32069         target.insertBefore(item.el.dom, position || null);
32070         item.container = Ext.get(target);
32071         this.configureItem(item);
32072         this.childrenChanged = true;
32073     },
32074
32075     /**
32076      * @private
32077      * Adds the layout's targetCls if necessary and sets
32078      * initialized flag when complete.
32079      */
32080     initLayout : function() {
32081         var me = this,
32082             targetCls = me.targetCls;
32083             
32084         if (!me.initialized && !Ext.isEmpty(targetCls)) {
32085             me.getTarget().addCls(targetCls);
32086         }
32087         me.initialized = true;
32088     },
32089
32090     // @private Sets the layout owner
32091     setOwner : function(owner) {
32092         this.owner = owner;
32093     },
32094
32095     // @private - Returns empty array
32096     getLayoutItems : function() {
32097         return [];
32098     },
32099
32100     /**
32101      * @private
32102      * Applies itemCls
32103      * Empty template method
32104      */
32105     configureItem: Ext.emptyFn,
32106     
32107     // Placeholder empty functions for subclasses to extend
32108     onLayout : Ext.emptyFn,
32109     afterLayout : Ext.emptyFn,
32110     onRemove : Ext.emptyFn,
32111     onDestroy : Ext.emptyFn,
32112     doOwnerCtLayouts : Ext.emptyFn,
32113
32114     /**
32115      * @private
32116      * Removes itemCls
32117      */
32118     afterRemove : function(item) {
32119         var el = item.el,
32120             owner = this.owner,
32121             itemCls = this.itemCls,
32122             ownerCls = owner.itemCls;
32123             
32124         // Clear managed dimensions flag when removed from the layout.
32125         if (item.rendered && !item.isDestroyed) {
32126             if (itemCls) {
32127                 el.removeCls(itemCls);
32128             }
32129             if (ownerCls) {
32130                 el.removeCls(ownerCls);
32131             }
32132         }
32133
32134         // These flags are set at the time a child item is added to a layout.
32135         // The layout must decide if it is managing the item's width, or its height, or both.
32136         // See AbstractComponent for docs on these properties.
32137         delete item.layoutManagedWidth;
32138         delete item.layoutManagedHeight;
32139     },
32140
32141     /**
32142      * Destroys this layout. This is a template method that is empty by default, but should be implemented
32143      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
32144      * @template
32145      */
32146     destroy : function() {
32147         var targetCls = this.targetCls,
32148             target;
32149         
32150         if (!Ext.isEmpty(targetCls)) {
32151             target = this.getTarget();
32152             if (target) {
32153                 target.removeCls(targetCls);
32154             }
32155         }
32156         this.onDestroy();
32157     }
32158 });
32159 /**
32160  * @class Ext.ZIndexManager
32161  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32162  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32163  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
32164  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32165  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32166  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32167  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
32168  * guaranteed to be correct, relative to the Window.</p>
32169  */
32170 Ext.define('Ext.ZIndexManager', {
32171
32172     alternateClassName: 'Ext.WindowGroup',
32173
32174     statics: {
32175         zBase : 9000
32176     },
32177
32178     constructor: function(container) {
32179         var me = this;
32180
32181         me.list = {};
32182         me.zIndexStack = [];
32183         me.front = null;
32184
32185         if (container) {
32186
32187             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32188             if (container.isContainer) {
32189                 container.on('resize', me._onContainerResize, me);
32190                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32191                 // The containing element we will be dealing with (eg masking) is the content target
32192                 me.targetEl = container.getTargetEl();
32193                 me.container = container;
32194             }
32195             // This is the ZIndexManager for a DOM element
32196             else {
32197                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32198                 me.zseed = me.getNextZSeed();
32199                 me.targetEl = Ext.get(container);
32200             }
32201         }
32202         // No container passed means we are the global WindowManager. Our target is the doc body.
32203         // DOM must be ready to collect that ref.
32204         else {
32205             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32206             me.zseed = me.getNextZSeed();
32207             Ext.onDocumentReady(function() {
32208                 me.targetEl = Ext.getBody();
32209             });
32210         }
32211     },
32212
32213     getNextZSeed: function() {
32214         return (Ext.ZIndexManager.zBase += 10000);
32215     },
32216
32217     setBase: function(baseZIndex) {
32218         this.zseed = baseZIndex;
32219         return this.assignZIndices();
32220     },
32221
32222     // private
32223     assignZIndices: function() {
32224         var a = this.zIndexStack,
32225             len = a.length,
32226             i = 0,
32227             zIndex = this.zseed,
32228             comp;
32229
32230         for (; i < len; i++) {
32231             comp = a[i];
32232             if (comp && !comp.hidden) {
32233
32234                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32235                 // that Component.
32236                 // If it's just a plain floating Component such as a BoundList, then the
32237                 // return value is the passed value plus 10, ready for the next item.
32238                 // If a floating *Container* has its zIndex set, it re-orders its managed
32239                 // floating children, starting from that new base, and returns a value 10000 above
32240                 // the highest zIndex which it allocates.
32241                 zIndex = comp.setZIndex(zIndex);
32242             }
32243         }
32244         this._activateLast();
32245         return zIndex;
32246     },
32247
32248     // private
32249     _setActiveChild: function(comp) {
32250         if (comp !== this.front) {
32251
32252             if (this.front) {
32253                 this.front.setActive(false, comp);
32254             }
32255             this.front = comp;
32256             if (comp) {
32257                 comp.setActive(true);
32258                 if (comp.modal) {
32259                     this._showModalMask(comp);
32260                 }
32261             }
32262         }
32263     },
32264
32265     // private
32266     _activateLast: function(justHidden) {
32267         var comp,
32268             lastActivated = false,
32269             i;
32270
32271         // Go down through the z-index stack.
32272         // Activate the next visible one down.
32273         // Keep going down to find the next visible modal one to shift the modal mask down under
32274         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32275             comp = this.zIndexStack[i];
32276             if (!comp.hidden) {
32277                 if (!lastActivated) {
32278                     this._setActiveChild(comp);
32279                     lastActivated = true;
32280                 }
32281
32282                 // Move any modal mask down to just under the next modal floater down the stack
32283                 if (comp.modal) {
32284                     this._showModalMask(comp);
32285                     return;
32286                 }
32287             }
32288         }
32289
32290         // none to activate, so there must be no modal mask.
32291         // And clear the currently active property
32292         this._hideModalMask();
32293         if (!lastActivated) {
32294             this._setActiveChild(null);
32295         }
32296     },
32297
32298     _showModalMask: function(comp) {
32299         var zIndex = comp.el.getStyle('zIndex') - 4,
32300             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
32301             parentBox;
32302         
32303         if (!maskTarget) {
32304             return;
32305         }
32306         
32307         parentBox = maskTarget.getBox();
32308
32309         if (!this.mask) {
32310             this.mask = Ext.getBody().createChild({
32311                 cls: Ext.baseCSSPrefix + 'mask'
32312             });
32313             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
32314             this.mask.on('click', this._onMaskClick, this);
32315         }
32316         if (maskTarget.dom === document.body) {
32317             parentBox.height = Ext.Element.getViewHeight();
32318         }
32319         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
32320         this.mask.setBox(parentBox);
32321         this.mask.setStyle('zIndex', zIndex);
32322         this.mask.show();
32323     },
32324
32325     _hideModalMask: function() {
32326         if (this.mask && this.mask.dom.parentNode) {
32327             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
32328             this.mask.hide();
32329         }
32330     },
32331
32332     _onMaskClick: function() {
32333         if (this.front) {
32334             this.front.focus();
32335         }
32336     },
32337
32338     _onContainerResize: function() {
32339         if (this.mask && this.mask.isVisible()) {
32340             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
32341         }
32342     },
32343
32344     /**
32345      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32346      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32347      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32348      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32349      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32350      * ZIndexManager in the desktop sample app:</p><code><pre>
32351 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32352 </pre></code>
32353      * @param {Ext.Component} comp The Component to register.
32354      */
32355     register : function(comp) {
32356         if (comp.zIndexManager) {
32357             comp.zIndexManager.unregister(comp);
32358         }
32359         comp.zIndexManager = this;
32360
32361         this.list[comp.id] = comp;
32362         this.zIndexStack.push(comp);
32363         comp.on('hide', this._activateLast, this);
32364     },
32365
32366     /**
32367      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32368      * need to be called. Components are automatically unregistered upon destruction.
32369      * See {@link #register}.</p>
32370      * @param {Ext.Component} comp The Component to unregister.
32371      */
32372     unregister : function(comp) {
32373         delete comp.zIndexManager;
32374         if (this.list && this.list[comp.id]) {
32375             delete this.list[comp.id];
32376             comp.un('hide', this._activateLast);
32377             Ext.Array.remove(this.zIndexStack, comp);
32378
32379             // Destruction requires that the topmost visible floater be activated. Same as hiding.
32380             this._activateLast(comp);
32381         }
32382     },
32383
32384     /**
32385      * Gets a registered Component by id.
32386      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
32387      * @return {Ext.Component}
32388      */
32389     get : function(id) {
32390         return typeof id == "object" ? id : this.list[id];
32391     },
32392
32393    /**
32394      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
32395      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32396      * @return {Boolean} True if the dialog was brought to the front, else false
32397      * if it was already in front
32398      */
32399     bringToFront : function(comp) {
32400         comp = this.get(comp);
32401         if (comp !== this.front) {
32402             Ext.Array.remove(this.zIndexStack, comp);
32403             this.zIndexStack.push(comp);
32404             this.assignZIndices();
32405             return true;
32406         }
32407         if (comp.modal) {
32408             this._showModalMask(comp);
32409         }
32410         return false;
32411     },
32412
32413     /**
32414      * Sends the specified Component to the back of other active Components in this ZIndexManager.
32415      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32416      * @return {Ext.Component} The Component
32417      */
32418     sendToBack : function(comp) {
32419         comp = this.get(comp);
32420         Ext.Array.remove(this.zIndexStack, comp);
32421         this.zIndexStack.unshift(comp);
32422         this.assignZIndices();
32423         return comp;
32424     },
32425
32426     /**
32427      * Hides all Components managed by this ZIndexManager.
32428      */
32429     hideAll : function() {
32430         for (var id in this.list) {
32431             if (this.list[id].isComponent && this.list[id].isVisible()) {
32432                 this.list[id].hide();
32433             }
32434         }
32435     },
32436
32437     /**
32438      * @private
32439      * Temporarily hides all currently visible managed Components. This is for when
32440      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
32441      * they should all be hidden just for the duration of the drag.
32442      */
32443     hide: function() {
32444         var i = 0,
32445             ln = this.zIndexStack.length,
32446             comp;
32447
32448         this.tempHidden = [];
32449         for (; i < ln; i++) {
32450             comp = this.zIndexStack[i];
32451             if (comp.isVisible()) {
32452                 this.tempHidden.push(comp);
32453                 comp.hide();
32454             }
32455         }
32456     },
32457
32458     /**
32459      * @private
32460      * Restores temporarily hidden managed Components to visibility.
32461      */
32462     show: function() {
32463         var i = 0,
32464             ln = this.tempHidden.length,
32465             comp,
32466             x,
32467             y;
32468
32469         for (; i < ln; i++) {
32470             comp = this.tempHidden[i];
32471             x = comp.x;
32472             y = comp.y;
32473             comp.show();
32474             comp.setPosition(x, y);
32475         }
32476         delete this.tempHidden;
32477     },
32478
32479     /**
32480      * Gets the currently-active Component in this ZIndexManager.
32481      * @return {Ext.Component} The active Component
32482      */
32483     getActive : function() {
32484         return this.front;
32485     },
32486
32487     /**
32488      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
32489      * The function should accept a single {@link Ext.Component} reference as its only argument and should
32490      * return true if the Component matches the search criteria, otherwise it should return false.
32491      * @param {Function} fn The search function
32492      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
32493      * that gets passed to the function if not specified)
32494      * @return {Array} An array of zero or more matching windows
32495      */
32496     getBy : function(fn, scope) {
32497         var r = [],
32498             i = 0,
32499             len = this.zIndexStack.length,
32500             comp;
32501
32502         for (; i < len; i++) {
32503             comp = this.zIndexStack[i];
32504             if (fn.call(scope||comp, comp) !== false) {
32505                 r.push(comp);
32506             }
32507         }
32508         return r;
32509     },
32510
32511     /**
32512      * Executes the specified function once for every Component in this ZIndexManager, passing each
32513      * Component as the only parameter. Returning false from the function will stop the iteration.
32514      * @param {Function} fn The function to execute for each item
32515      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
32516      */
32517     each : function(fn, scope) {
32518         var comp;
32519         for (var id in this.list) {
32520             comp = this.list[id];
32521             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32522                 return;
32523             }
32524         }
32525     },
32526
32527     /**
32528      * Executes the specified function once for every Component in this ZIndexManager, passing each
32529      * Component as the only parameter. Returning false from the function will stop the iteration.
32530      * The components are passed to the function starting at the bottom and proceeding to the top.
32531      * @param {Function} fn The function to execute for each item
32532      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32533      * is executed. Defaults to the current Component in the iteration.
32534      */
32535     eachBottomUp: function (fn, scope) {
32536         var comp,
32537             stack = this.zIndexStack,
32538             i, n;
32539
32540         for (i = 0, n = stack.length ; i < n; i++) {
32541             comp = stack[i];
32542             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32543                 return;
32544             }
32545         }
32546     },
32547
32548     /**
32549      * Executes the specified function once for every Component in this ZIndexManager, passing each
32550      * Component as the only parameter. Returning false from the function will stop the iteration.
32551      * The components are passed to the function starting at the top and proceeding to the bottom.
32552      * @param {Function} fn The function to execute for each item
32553      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32554      * is executed. Defaults to the current Component in the iteration.
32555      */
32556     eachTopDown: function (fn, scope) {
32557         var comp,
32558             stack = this.zIndexStack,
32559             i;
32560
32561         for (i = stack.length ; i-- > 0; ) {
32562             comp = stack[i];
32563             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32564                 return;
32565             }
32566         }
32567     },
32568
32569     destroy: function() {
32570         this.each(function(c) {
32571             c.destroy();
32572         });
32573         delete this.zIndexStack;
32574         delete this.list;
32575         delete this.container;
32576         delete this.targetEl;
32577     }
32578 }, function() {
32579     /**
32580      * @class Ext.WindowManager
32581      * @extends Ext.ZIndexManager
32582      * <p>The default global floating Component group that is available automatically.</p>
32583      * <p>This manages instances of floating Components which were rendered programatically without
32584      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
32585      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
32586      * there are managed by that ZIndexManager.</p>
32587      * @singleton
32588      */
32589     Ext.WindowManager = Ext.WindowMgr = new this();
32590 });
32591
32592 /**
32593  * @private
32594  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
32595  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
32596  * for its container.
32597  */
32598 Ext.define('Ext.layout.container.boxOverflow.None', {
32599     
32600     alternateClassName: 'Ext.layout.boxOverflow.None',
32601     
32602     constructor: function(layout, config) {
32603         this.layout = layout;
32604         Ext.apply(this, config || {});
32605     },
32606
32607     handleOverflow: Ext.emptyFn,
32608
32609     clearOverflow: Ext.emptyFn,
32610     
32611     onRemove: Ext.emptyFn,
32612
32613     /**
32614      * @private
32615      * Normalizes an item reference, string id or numerical index into a reference to the item
32616      * @param {Ext.Component/String/Number} item The item reference, id or index
32617      * @return {Ext.Component} The item
32618      */
32619     getItem: function(item) {
32620         return this.layout.owner.getComponent(item);
32621     },
32622     
32623     onRemove: Ext.emptyFn
32624 });
32625 /**
32626  * @class Ext.util.KeyMap
32627  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
32628  * The constructor accepts the same config object as defined by {@link #addBinding}.
32629  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
32630  * combination it will call the function with this signature (if the match is a multi-key
32631  * combination the callback will still be called only once): (String key, Ext.EventObject e)
32632  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
32633  * Usage:
32634  <pre><code>
32635 // map one key by key code
32636 var map = new Ext.util.KeyMap("my-element", {
32637     key: 13, // or Ext.EventObject.ENTER
32638     fn: myHandler,
32639     scope: myObject
32640 });
32641
32642 // map multiple keys to one action by string
32643 var map = new Ext.util.KeyMap("my-element", {
32644     key: "a\r\n\t",
32645     fn: myHandler,
32646     scope: myObject
32647 });
32648
32649 // map multiple keys to multiple actions by strings and array of codes
32650 var map = new Ext.util.KeyMap("my-element", [
32651     {
32652         key: [10,13],
32653         fn: function(){ alert("Return was pressed"); }
32654     }, {
32655         key: "abc",
32656         fn: function(){ alert('a, b or c was pressed'); }
32657     }, {
32658         key: "\t",
32659         ctrl:true,
32660         shift:true,
32661         fn: function(){ alert('Control + shift + tab was pressed.'); }
32662     }
32663 ]);
32664 </code></pre>
32665  */
32666 Ext.define('Ext.util.KeyMap', {
32667     alternateClassName: 'Ext.KeyMap',
32668
32669     /**
32670      * Creates new KeyMap.
32671      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
32672      * @param {Object} binding The binding (see {@link #addBinding})
32673      * @param {String} [eventName="keydown"] The event to bind to
32674      */
32675     constructor: function(el, binding, eventName){
32676         var me = this;
32677
32678         Ext.apply(me, {
32679             el: Ext.get(el),
32680             eventName: eventName || me.eventName,
32681             bindings: []
32682         });
32683         if (binding) {
32684             me.addBinding(binding);
32685         }
32686         me.enable();
32687     },
32688
32689     eventName: 'keydown',
32690
32691     /**
32692      * Add a new binding to this KeyMap. The following config object properties are supported:
32693      * <pre>
32694 Property            Type             Description
32695 ----------          ---------------  ----------------------------------------------------------------------
32696 key                 String/Array     A single keycode or an array of keycodes to handle
32697 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)
32698 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)
32699 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)
32700 handler             Function         The function to call when KeyMap finds the expected key combination
32701 fn                  Function         Alias of handler (for backwards-compatibility)
32702 scope               Object           The scope of the callback function
32703 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.
32704 </pre>
32705      *
32706      * Usage:
32707      * <pre><code>
32708 // Create a KeyMap
32709 var map = new Ext.util.KeyMap(document, {
32710     key: Ext.EventObject.ENTER,
32711     fn: handleKey,
32712     scope: this
32713 });
32714
32715 //Add a new binding to the existing KeyMap later
32716 map.addBinding({
32717     key: 'abc',
32718     shift: true,
32719     fn: handleKey,
32720     scope: this
32721 });
32722 </code></pre>
32723      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
32724      */
32725     addBinding : function(binding){
32726         if (Ext.isArray(binding)) {
32727             Ext.each(binding, this.addBinding, this);
32728             return;
32729         }
32730
32731         var keyCode = binding.key,
32732             processed = false,
32733             key,
32734             keys,
32735             keyString,
32736             i,
32737             len;
32738
32739         if (Ext.isString(keyCode)) {
32740             keys = [];
32741             keyString = keyCode.toUpperCase();
32742
32743             for (i = 0, len = keyString.length; i < len; ++i){
32744                 keys.push(keyString.charCodeAt(i));
32745             }
32746             keyCode = keys;
32747             processed = true;
32748         }
32749
32750         if (!Ext.isArray(keyCode)) {
32751             keyCode = [keyCode];
32752         }
32753
32754         if (!processed) {
32755             for (i = 0, len = keyCode.length; i < len; ++i) {
32756                 key = keyCode[i];
32757                 if (Ext.isString(key)) {
32758                     keyCode[i] = key.toUpperCase().charCodeAt(0);
32759                 }
32760             }
32761         }
32762
32763         this.bindings.push(Ext.apply({
32764             keyCode: keyCode
32765         }, binding));
32766     },
32767
32768     /**
32769      * Process any keydown events on the element
32770      * @private
32771      * @param {Ext.EventObject} event
32772      */
32773     handleKeyDown: function(event) {
32774         if (this.enabled) { //just in case
32775             var bindings = this.bindings,
32776                 i = 0,
32777                 len = bindings.length;
32778
32779             event = this.processEvent(event);
32780             for(; i < len; ++i){
32781                 this.processBinding(bindings[i], event);
32782             }
32783         }
32784     },
32785
32786     /**
32787      * Ugly hack to allow this class to be tested. Currently WebKit gives
32788      * no way to raise a key event properly with both
32789      * a) A keycode
32790      * b) The alt/ctrl/shift modifiers
32791      * So we have to simulate them here. Yuk!
32792      * This is a stub method intended to be overridden by tests.
32793      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
32794      * @private
32795      */
32796     processEvent: function(event){
32797         return event;
32798     },
32799
32800     /**
32801      * Process a particular binding and fire the handler if necessary.
32802      * @private
32803      * @param {Object} binding The binding information
32804      * @param {Ext.EventObject} event
32805      */
32806     processBinding: function(binding, event){
32807         if (this.checkModifiers(binding, event)) {
32808             var key = event.getKey(),
32809                 handler = binding.fn || binding.handler,
32810                 scope = binding.scope || this,
32811                 keyCode = binding.keyCode,
32812                 defaultEventAction = binding.defaultEventAction,
32813                 i,
32814                 len,
32815                 keydownEvent = new Ext.EventObjectImpl(event);
32816
32817
32818             for (i = 0, len = keyCode.length; i < len; ++i) {
32819                 if (key === keyCode[i]) {
32820                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
32821                         keydownEvent[defaultEventAction]();
32822                     }
32823                     break;
32824                 }
32825             }
32826         }
32827     },
32828
32829     /**
32830      * Check if the modifiers on the event match those on the binding
32831      * @private
32832      * @param {Object} binding
32833      * @param {Ext.EventObject} event
32834      * @return {Boolean} True if the event matches the binding
32835      */
32836     checkModifiers: function(binding, e){
32837         var keys = ['shift', 'ctrl', 'alt'],
32838             i = 0,
32839             len = keys.length,
32840             val, key;
32841
32842         for (; i < len; ++i){
32843             key = keys[i];
32844             val = binding[key];
32845             if (!(val === undefined || (val === e[key + 'Key']))) {
32846                 return false;
32847             }
32848         }
32849         return true;
32850     },
32851
32852     /**
32853      * Shorthand for adding a single key listener
32854      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
32855      * following options:
32856      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
32857      * @param {Function} fn The function to call
32858      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
32859      */
32860     on: function(key, fn, scope) {
32861         var keyCode, shift, ctrl, alt;
32862         if (Ext.isObject(key) && !Ext.isArray(key)) {
32863             keyCode = key.key;
32864             shift = key.shift;
32865             ctrl = key.ctrl;
32866             alt = key.alt;
32867         } else {
32868             keyCode = key;
32869         }
32870         this.addBinding({
32871             key: keyCode,
32872             shift: shift,
32873             ctrl: ctrl,
32874             alt: alt,
32875             fn: fn,
32876             scope: scope
32877         });
32878     },
32879
32880     /**
32881      * Returns true if this KeyMap is enabled
32882      * @return {Boolean}
32883      */
32884     isEnabled : function(){
32885         return this.enabled;
32886     },
32887
32888     /**
32889      * Enables this KeyMap
32890      */
32891     enable: function(){
32892         var me = this;
32893         
32894         if (!me.enabled) {
32895             me.el.on(me.eventName, me.handleKeyDown, me);
32896             me.enabled = true;
32897         }
32898     },
32899
32900     /**
32901      * Disable this KeyMap
32902      */
32903     disable: function(){
32904         var me = this;
32905         
32906         if (me.enabled) {
32907             me.el.removeListener(me.eventName, me.handleKeyDown, me);
32908             me.enabled = false;
32909         }
32910     },
32911
32912     /**
32913      * Convenience function for setting disabled/enabled by boolean.
32914      * @param {Boolean} disabled
32915      */
32916     setDisabled : function(disabled){
32917         if (disabled) {
32918             this.disable();
32919         } else {
32920             this.enable();
32921         }
32922     },
32923
32924     /**
32925      * Destroys the KeyMap instance and removes all handlers.
32926      * @param {Boolean} removeEl True to also remove the attached element
32927      */
32928     destroy: function(removeEl){
32929         var me = this;
32930
32931         me.bindings = [];
32932         me.disable();
32933         if (removeEl === true) {
32934             me.el.remove();
32935         }
32936         delete me.el;
32937     }
32938 });
32939 /**
32940  * @class Ext.util.ClickRepeater
32941  * @extends Ext.util.Observable
32942  *
32943  * A wrapper class which can be applied to any element. Fires a "click" event while the
32944  * mouse is pressed. The interval between firings may be specified in the config but
32945  * defaults to 20 milliseconds.
32946  *
32947  * Optionally, a CSS class may be applied to the element during the time it is pressed.
32948  *
32949  */
32950 Ext.define('Ext.util.ClickRepeater', {
32951     extend: 'Ext.util.Observable',
32952
32953     /**
32954      * Creates new ClickRepeater.
32955      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
32956      * @param {Object} config (optional) Config object.
32957      */
32958     constructor : function(el, config){
32959         this.el = Ext.get(el);
32960         this.el.unselectable();
32961
32962         Ext.apply(this, config);
32963
32964         this.addEvents(
32965         /**
32966          * @event mousedown
32967          * Fires when the mouse button is depressed.
32968          * @param {Ext.util.ClickRepeater} this
32969          * @param {Ext.EventObject} e
32970          */
32971         "mousedown",
32972         /**
32973          * @event click
32974          * Fires on a specified interval during the time the element is pressed.
32975          * @param {Ext.util.ClickRepeater} this
32976          * @param {Ext.EventObject} e
32977          */
32978         "click",
32979         /**
32980          * @event mouseup
32981          * Fires when the mouse key is released.
32982          * @param {Ext.util.ClickRepeater} this
32983          * @param {Ext.EventObject} e
32984          */
32985         "mouseup"
32986         );
32987
32988         if(!this.disabled){
32989             this.disabled = true;
32990             this.enable();
32991         }
32992
32993         // allow inline handler
32994         if(this.handler){
32995             this.on("click", this.handler,  this.scope || this);
32996         }
32997
32998         this.callParent();
32999     },
33000
33001     /**
33002      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
33003      */
33004
33005     /**
33006      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
33007      */
33008
33009     /**
33010      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
33011      * "interval" and "delay" are ignored.
33012      */
33013
33014     /**
33015      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
33016      */
33017     interval : 20,
33018
33019     /**
33020      * @cfg {Number} delay The initial delay before the repeating event begins firing.
33021      * Similar to an autorepeat key delay.
33022      */
33023     delay: 250,
33024
33025     /**
33026      * @cfg {Boolean} preventDefault True to prevent the default click event
33027      */
33028     preventDefault : true,
33029     /**
33030      * @cfg {Boolean} stopDefault True to stop the default click event
33031      */
33032     stopDefault : false,
33033
33034     timer : 0,
33035
33036     /**
33037      * Enables the repeater and allows events to fire.
33038      */
33039     enable: function(){
33040         if(this.disabled){
33041             this.el.on('mousedown', this.handleMouseDown, this);
33042             if (Ext.isIE){
33043                 this.el.on('dblclick', this.handleDblClick, this);
33044             }
33045             if(this.preventDefault || this.stopDefault){
33046                 this.el.on('click', this.eventOptions, this);
33047             }
33048         }
33049         this.disabled = false;
33050     },
33051
33052     /**
33053      * Disables the repeater and stops events from firing.
33054      */
33055     disable: function(/* private */ force){
33056         if(force || !this.disabled){
33057             clearTimeout(this.timer);
33058             if(this.pressedCls){
33059                 this.el.removeCls(this.pressedCls);
33060             }
33061             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33062             this.el.removeAllListeners();
33063         }
33064         this.disabled = true;
33065     },
33066
33067     /**
33068      * Convenience function for setting disabled/enabled by boolean.
33069      * @param {Boolean} disabled
33070      */
33071     setDisabled: function(disabled){
33072         this[disabled ? 'disable' : 'enable']();
33073     },
33074
33075     eventOptions: function(e){
33076         if(this.preventDefault){
33077             e.preventDefault();
33078         }
33079         if(this.stopDefault){
33080             e.stopEvent();
33081         }
33082     },
33083
33084     // private
33085     destroy : function() {
33086         this.disable(true);
33087         Ext.destroy(this.el);
33088         this.clearListeners();
33089     },
33090
33091     handleDblClick : function(e){
33092         clearTimeout(this.timer);
33093         this.el.blur();
33094
33095         this.fireEvent("mousedown", this, e);
33096         this.fireEvent("click", this, e);
33097     },
33098
33099     // private
33100     handleMouseDown : function(e){
33101         clearTimeout(this.timer);
33102         this.el.blur();
33103         if(this.pressedCls){
33104             this.el.addCls(this.pressedCls);
33105         }
33106         this.mousedownTime = new Date();
33107
33108         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33109         this.el.on("mouseout", this.handleMouseOut, this);
33110
33111         this.fireEvent("mousedown", this, e);
33112         this.fireEvent("click", this, e);
33113
33114         // Do not honor delay or interval if acceleration wanted.
33115         if (this.accelerate) {
33116             this.delay = 400;
33117         }
33118
33119         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33120         // the global shared EventObject gets a new Event put into it before the timer fires.
33121         e = new Ext.EventObjectImpl(e);
33122
33123         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33124     },
33125
33126     // private
33127     click : function(e){
33128         this.fireEvent("click", this, e);
33129         this.timer =  Ext.defer(this.click, this.accelerate ?
33130             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33131                 400,
33132                 -390,
33133                 12000) :
33134             this.interval, this, [e]);
33135     },
33136
33137     easeOutExpo : function (t, b, c, d) {
33138         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33139     },
33140
33141     // private
33142     handleMouseOut : function(){
33143         clearTimeout(this.timer);
33144         if(this.pressedCls){
33145             this.el.removeCls(this.pressedCls);
33146         }
33147         this.el.on("mouseover", this.handleMouseReturn, this);
33148     },
33149
33150     // private
33151     handleMouseReturn : function(){
33152         this.el.un("mouseover", this.handleMouseReturn, this);
33153         if(this.pressedCls){
33154             this.el.addCls(this.pressedCls);
33155         }
33156         this.click();
33157     },
33158
33159     // private
33160     handleMouseUp : function(e){
33161         clearTimeout(this.timer);
33162         this.el.un("mouseover", this.handleMouseReturn, this);
33163         this.el.un("mouseout", this.handleMouseOut, this);
33164         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33165         if(this.pressedCls){
33166             this.el.removeCls(this.pressedCls);
33167         }
33168         this.fireEvent("mouseup", this, e);
33169     }
33170 });
33171
33172 /**
33173  * @class Ext.layout.component.Component
33174  * @extends Ext.layout.Layout
33175  *
33176  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
33177  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
33178  *
33179  * @private
33180  */
33181 Ext.define('Ext.layout.component.Component', {
33182
33183     /* Begin Definitions */
33184
33185     extend: 'Ext.layout.Layout',
33186
33187     /* End Definitions */
33188
33189     type: 'component',
33190
33191     monitorChildren: true,
33192
33193     initLayout : function() {
33194         var me = this,
33195             owner = me.owner,
33196             ownerEl = owner.el;
33197
33198         if (!me.initialized) {
33199             if (owner.frameSize) {
33200                 me.frameSize = owner.frameSize;
33201             }
33202             else {
33203                 owner.frameSize = me.frameSize = {
33204                     top: 0,
33205                     left: 0,
33206                     bottom: 0,
33207                     right: 0
33208                 };
33209             }
33210         }
33211         me.callParent(arguments);
33212     },
33213
33214     beforeLayout : function(width, height, isSetSize, callingContainer) {
33215         this.callParent(arguments);
33216
33217         var me = this,
33218             owner = me.owner,
33219             ownerCt = owner.ownerCt,
33220             layout = owner.layout,
33221             isVisible = owner.isVisible(true),
33222             ownerElChild = owner.el.child,
33223             layoutCollection;
33224
33225         // Cache the size we began with so we can see if there has been any effect.
33226         me.previousComponentSize = me.lastComponentSize;
33227
33228         // Do not allow autoing of any dimensions which are fixed
33229         if (!isSetSize
33230             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
33231                 (!Ext.isNumber(height) && owner.isFixedHeight()))
33232             // unless we are being told to do so by the ownerCt's layout
33233             && callingContainer && callingContainer !== ownerCt) {
33234             
33235             me.doContainerLayout();
33236             return false;
33237         }
33238
33239         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
33240         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
33241         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
33242             if (owner.hiddenAncestor) {
33243                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
33244                 layoutCollection.remove(owner);
33245                 layoutCollection.add(owner);
33246             }
33247             owner.needsLayout = {
33248                 width: width,
33249                 height: height,
33250                 isSetSize: false
33251             };
33252         }
33253
33254         if (isVisible && this.needsLayout(width, height)) {
33255             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
33256         }
33257         else {
33258             return false;
33259         }
33260     },
33261
33262     /**
33263     * Check if the new size is different from the current size and only
33264     * trigger a layout if it is necessary.
33265     * @param {Number} width The new width to set.
33266     * @param {Number} height The new height to set.
33267     */
33268     needsLayout : function(width, height) {
33269         var me = this,
33270             widthBeingChanged,
33271             heightBeingChanged;
33272             me.lastComponentSize = me.lastComponentSize || {
33273                 width: -Infinity,
33274                 height: -Infinity
33275             };
33276
33277         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
33278         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
33279
33280         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
33281         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
33282
33283
33284         // isSizing flag added to prevent redundant layouts when going up the layout chain
33285         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
33286     },
33287
33288     /**
33289     * Set the size of any element supporting undefined, null, and values.
33290     * @param {Number} width The new width to set.
33291     * @param {Number} height The new height to set.
33292     */
33293     setElementSize: function(el, width, height) {
33294         if (width !== undefined && height !== undefined) {
33295             el.setSize(width, height);
33296         }
33297         else if (height !== undefined) {
33298             el.setHeight(height);
33299         }
33300         else if (width !== undefined) {
33301             el.setWidth(width);
33302         }
33303     },
33304
33305     /**
33306      * Returns the owner component's resize element.
33307      * @return {Ext.Element}
33308      */
33309      getTarget : function() {
33310          return this.owner.el;
33311      },
33312
33313     /**
33314      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
33315      * May be overridden in Component layout managers which implement an inner element.
33316      * @return {Ext.Element}
33317      */
33318     getRenderTarget : function() {
33319         return this.owner.el;
33320     },
33321
33322     /**
33323     * Set the size of the target element.
33324     * @param {Number} width The new width to set.
33325     * @param {Number} height The new height to set.
33326     */
33327     setTargetSize : function(width, height) {
33328         var me = this;
33329         me.setElementSize(me.owner.el, width, height);
33330
33331         if (me.owner.frameBody) {
33332             var targetInfo = me.getTargetInfo(),
33333                 padding = targetInfo.padding,
33334                 border = targetInfo.border,
33335                 frameSize = me.frameSize;
33336
33337             me.setElementSize(me.owner.frameBody,
33338                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
33339                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
33340             );
33341         }
33342
33343         me.autoSized = {
33344             width: !Ext.isNumber(width),
33345             height: !Ext.isNumber(height)
33346         };
33347
33348         me.lastComponentSize = {
33349             width: width,
33350             height: height
33351         };
33352     },
33353
33354     getTargetInfo : function() {
33355         if (!this.targetInfo) {
33356             var target = this.getTarget(),
33357                 body = this.owner.getTargetEl();
33358
33359             this.targetInfo = {
33360                 padding: {
33361                     top: target.getPadding('t'),
33362                     right: target.getPadding('r'),
33363                     bottom: target.getPadding('b'),
33364                     left: target.getPadding('l')
33365                 },
33366                 border: {
33367                     top: target.getBorderWidth('t'),
33368                     right: target.getBorderWidth('r'),
33369                     bottom: target.getBorderWidth('b'),
33370                     left: target.getBorderWidth('l')
33371                 },
33372                 bodyMargin: {
33373                     top: body.getMargin('t'),
33374                     right: body.getMargin('r'),
33375                     bottom: body.getMargin('b'),
33376                     left: body.getMargin('l')
33377                 }
33378             };
33379         }
33380         return this.targetInfo;
33381     },
33382
33383     // Start laying out UP the ownerCt's layout when flagged to do so.
33384     doOwnerCtLayouts: function() {
33385         var owner = this.owner,
33386             ownerCt = owner.ownerCt,
33387             ownerCtComponentLayout, ownerCtContainerLayout,
33388             curSize = this.lastComponentSize,
33389             prevSize = this.previousComponentSize,
33390             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
33391             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
33392
33393         // If size has not changed, do not inform upstream layouts
33394         if (!ownerCt || (!widthChange && !heightChange)) {
33395             return;
33396         }
33397
33398         ownerCtComponentLayout = ownerCt.componentLayout;
33399         ownerCtContainerLayout = ownerCt.layout;
33400
33401         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33402             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
33403
33404                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
33405                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
33406                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
33407                     this.isSizing = true;
33408                     ownerCt.doComponentLayout();
33409                     this.isSizing = false;
33410                 }
33411                 // Execute upstream Container layout
33412                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
33413                     ownerCtContainerLayout.layout();
33414                 }
33415             }
33416         }
33417     },
33418
33419     doContainerLayout: function() {
33420         var me = this,
33421             owner = me.owner,
33422             ownerCt = owner.ownerCt,
33423             layout = owner.layout,
33424             ownerCtComponentLayout;
33425
33426         // Run the container layout if it exists (layout for child items)
33427         // **Unless automatic laying out is suspended, or the layout is currently running**
33428         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
33429             layout.layout();
33430         }
33431
33432         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
33433         if (ownerCt && ownerCt.componentLayout) {
33434             ownerCtComponentLayout = ownerCt.componentLayout;
33435             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33436                 ownerCtComponentLayout.childrenChanged = true;
33437             }
33438         }
33439     },
33440
33441     afterLayout : function(width, height, isSetSize, layoutOwner) {
33442         this.doContainerLayout();
33443         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
33444     }
33445 });
33446
33447 /**
33448  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
33449  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
33450  * should not contain any HTML, otherwise it may not be measured correctly.
33451  *
33452  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
33453  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
33454  * provide a **fixed width** when doing the measurement.
33455  *
33456  * If multiple measurements are being done on the same element, you create a new instance to initialize 
33457  * to avoid the overhead of copying the styles to the element repeatedly.
33458  */
33459 Ext.define('Ext.util.TextMetrics', {
33460     statics: {
33461         shared: null,
33462         /**
33463          * Measures the size of the specified text
33464          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
33465          * that can affect the size of the rendered text
33466          * @param {String} text The text to measure
33467          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
33468          * in order to accurately measure the text height
33469          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33470          */
33471         measure: function(el, text, fixedWidth){
33472             var me = this,
33473                 shared = me.shared;
33474             
33475             if(!shared){
33476                 shared = me.shared = new me(el, fixedWidth);
33477             }
33478             shared.bind(el);
33479             shared.setFixedWidth(fixedWidth || 'auto');
33480             return shared.getSize(text);
33481         },
33482         
33483         /**
33484           * Destroy the TextMetrics instance created by {@link #measure}.
33485           */
33486          destroy: function(){
33487              var me = this;
33488              Ext.destroy(me.shared);
33489              me.shared = null;
33490          }
33491     },
33492     
33493     /**
33494      * Creates new TextMetrics.
33495      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
33496      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
33497      */
33498     constructor: function(bindTo, fixedWidth){
33499         var measure = this.measure = Ext.getBody().createChild({
33500             cls: 'x-textmetrics'
33501         });
33502         this.el = Ext.get(bindTo);
33503         
33504         measure.position('absolute');
33505         measure.setLeftTop(-1000, -1000);
33506         measure.hide();
33507
33508         if (fixedWidth) {
33509            measure.setWidth(fixedWidth);
33510         }
33511     },
33512     
33513     /**
33514      * Returns the size of the specified text based on the internal element's style and width properties
33515      * @param {String} text The text to measure
33516      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33517      */
33518     getSize: function(text){
33519         var measure = this.measure,
33520             size;
33521         
33522         measure.update(text);
33523         size = measure.getSize();
33524         measure.update('');
33525         return size;
33526     },
33527     
33528     /**
33529      * Binds this TextMetrics instance to a new element
33530      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
33531      */
33532     bind: function(el){
33533         var me = this;
33534         
33535         me.el = Ext.get(el);
33536         me.measure.setStyle(
33537             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
33538         );
33539     },
33540     
33541     /**
33542      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
33543      * to set a fixed width in order to accurately measure the text height.
33544      * @param {Number} width The width to set on the element
33545      */
33546      setFixedWidth : function(width){
33547          this.measure.setWidth(width);
33548      },
33549      
33550      /**
33551       * Returns the measured width of the specified text
33552       * @param {String} text The text to measure
33553       * @return {Number} width The width in pixels
33554       */
33555      getWidth : function(text){
33556          this.measure.dom.style.width = 'auto';
33557          return this.getSize(text).width;
33558      },
33559      
33560      /**
33561       * Returns the measured height of the specified text
33562       * @param {String} text The text to measure
33563       * @return {Number} height The height in pixels
33564       */
33565      getHeight : function(text){
33566          return this.getSize(text).height;
33567      },
33568      
33569      /**
33570       * Destroy this instance
33571       */
33572      destroy: function(){
33573          var me = this;
33574          me.measure.remove();
33575          delete me.el;
33576          delete me.measure;
33577      }
33578 }, function(){
33579     Ext.Element.addMethods({
33580         /**
33581          * Returns the width in pixels of the passed text, or the width of the text in this Element.
33582          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
33583          * @param {Number} min (optional) The minumum value to return.
33584          * @param {Number} max (optional) The maximum value to return.
33585          * @return {Number} The text width in pixels.
33586          * @member Ext.Element
33587          */
33588         getTextWidth : function(text, min, max){
33589             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
33590         }
33591     });
33592 });
33593
33594 /**
33595  * @class Ext.layout.container.boxOverflow.Scroller
33596  * @extends Ext.layout.container.boxOverflow.None
33597  * @private
33598  */
33599 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
33600
33601     /* Begin Definitions */
33602
33603     extend: 'Ext.layout.container.boxOverflow.None',
33604     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
33605     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
33606     mixins: {
33607         observable: 'Ext.util.Observable'
33608     },
33609     
33610     /* End Definitions */
33611
33612     /**
33613      * @cfg {Boolean} animateScroll
33614      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
33615      */
33616     animateScroll: false,
33617
33618     /**
33619      * @cfg {Number} scrollIncrement
33620      * The number of pixels to scroll by on scroller click
33621      */
33622     scrollIncrement: 20,
33623
33624     /**
33625      * @cfg {Number} wheelIncrement
33626      * The number of pixels to increment on mouse wheel scrolling.
33627      */
33628     wheelIncrement: 10,
33629
33630     /**
33631      * @cfg {Number} scrollRepeatInterval
33632      * Number of milliseconds between each scroll while a scroller button is held down
33633      */
33634     scrollRepeatInterval: 60,
33635
33636     /**
33637      * @cfg {Number} scrollDuration
33638      * Number of milliseconds that each scroll animation lasts
33639      */
33640     scrollDuration: 400,
33641
33642     /**
33643      * @cfg {String} beforeCtCls
33644      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
33645      * which must always be present at the leftmost edge of the Container
33646      */
33647
33648     /**
33649      * @cfg {String} afterCtCls
33650      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
33651      * which must always be present at the rightmost edge of the Container
33652      */
33653
33654     /**
33655      * @cfg {String} [scrollerCls='x-box-scroller']
33656      * CSS class added to both scroller elements if enableScroll is used
33657      */
33658     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
33659
33660     /**
33661      * @cfg {String} beforeScrollerCls
33662      * CSS class added to the left scroller element if enableScroll is used
33663      */
33664
33665     /**
33666      * @cfg {String} afterScrollerCls
33667      * CSS class added to the right scroller element if enableScroll is used
33668      */
33669     
33670     constructor: function(layout, config) {
33671         this.layout = layout;
33672         Ext.apply(this, config || {});
33673         
33674         this.addEvents(
33675             /**
33676              * @event scroll
33677              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
33678              * @param {Number} newPosition The new position of the scroller
33679              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
33680              */
33681             'scroll'
33682         );
33683     },
33684     
33685     initCSSClasses: function() {
33686         var me = this,
33687         layout = me.layout;
33688
33689         if (!me.CSSinitialized) {
33690             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
33691             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
33692             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
33693             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
33694             me.CSSinitializes = true;
33695         }
33696     },
33697
33698     handleOverflow: function(calculations, targetSize) {
33699         var me = this,
33700             layout = me.layout,
33701             methodName = 'get' + layout.parallelPrefixCap,
33702             newSize = {};
33703
33704         me.initCSSClasses();
33705         me.callParent(arguments);
33706         this.createInnerElements();
33707         this.showScrollers();
33708         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
33709         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
33710         return { targetSize: newSize };
33711     },
33712
33713     /**
33714      * @private
33715      * Creates the beforeCt and afterCt elements if they have not already been created
33716      */
33717     createInnerElements: function() {
33718         var me = this,
33719             target = me.layout.getRenderTarget();
33720
33721         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
33722         //special items such as scrollers or dropdown menu triggers
33723         if (!me.beforeCt) {
33724             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
33725             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
33726             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
33727             me.createWheelListener();
33728         }
33729     },
33730
33731     /**
33732      * @private
33733      * Sets up an listener to scroll on the layout's innerCt mousewheel event
33734      */
33735     createWheelListener: function() {
33736         this.layout.innerCt.on({
33737             scope     : this,
33738             mousewheel: function(e) {
33739                 e.stopEvent();
33740
33741                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
33742             }
33743         });
33744     },
33745
33746     /**
33747      * @private
33748      */
33749     clearOverflow: function() {
33750         this.hideScrollers();
33751     },
33752
33753     /**
33754      * @private
33755      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
33756      * present. 
33757      */
33758     showScrollers: function() {
33759         this.createScrollers();
33760         this.beforeScroller.show();
33761         this.afterScroller.show();
33762         this.updateScrollButtons();
33763         
33764         this.layout.owner.addClsWithUI('scroller');
33765     },
33766
33767     /**
33768      * @private
33769      * Hides the scroller elements in the beforeCt and afterCt
33770      */
33771     hideScrollers: function() {
33772         if (this.beforeScroller != undefined) {
33773             this.beforeScroller.hide();
33774             this.afterScroller.hide();
33775             
33776             this.layout.owner.removeClsWithUI('scroller');
33777         }
33778     },
33779
33780     /**
33781      * @private
33782      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
33783      */
33784     createScrollers: function() {
33785         if (!this.beforeScroller && !this.afterScroller) {
33786             var before = this.beforeCt.createChild({
33787                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
33788             });
33789
33790             var after = this.afterCt.createChild({
33791                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
33792             });
33793
33794             before.addClsOnOver(this.beforeScrollerCls + '-hover');
33795             after.addClsOnOver(this.afterScrollerCls + '-hover');
33796
33797             before.setVisibilityMode(Ext.Element.DISPLAY);
33798             after.setVisibilityMode(Ext.Element.DISPLAY);
33799
33800             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
33801                 interval: this.scrollRepeatInterval,
33802                 handler : this.scrollLeft,
33803                 scope   : this
33804             });
33805
33806             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
33807                 interval: this.scrollRepeatInterval,
33808                 handler : this.scrollRight,
33809                 scope   : this
33810             });
33811
33812             /**
33813              * @property beforeScroller
33814              * @type Ext.Element
33815              * The left scroller element. Only created when needed.
33816              */
33817             this.beforeScroller = before;
33818
33819             /**
33820              * @property afterScroller
33821              * @type Ext.Element
33822              * The left scroller element. Only created when needed.
33823              */
33824             this.afterScroller = after;
33825         }
33826     },
33827
33828     /**
33829      * @private
33830      */
33831     destroy: function() {
33832         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
33833     },
33834
33835     /**
33836      * @private
33837      * Scrolls left or right by the number of pixels specified
33838      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
33839      */
33840     scrollBy: function(delta, animate) {
33841         this.scrollTo(this.getScrollPosition() + delta, animate);
33842     },
33843
33844     /**
33845      * @private
33846      * @return {Object} Object passed to scrollTo when scrolling
33847      */
33848     getScrollAnim: function() {
33849         return {
33850             duration: this.scrollDuration, 
33851             callback: this.updateScrollButtons, 
33852             scope   : this
33853         };
33854     },
33855
33856     /**
33857      * @private
33858      * Enables or disables each scroller button based on the current scroll position
33859      */
33860     updateScrollButtons: function() {
33861         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
33862             return;
33863         }
33864
33865         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
33866             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
33867             beforeCls  = this.beforeScrollerCls + '-disabled',
33868             afterCls   = this.afterScrollerCls  + '-disabled';
33869         
33870         this.beforeScroller[beforeMeth](beforeCls);
33871         this.afterScroller[afterMeth](afterCls);
33872         this.scrolling = false;
33873     },
33874
33875     /**
33876      * @private
33877      * Returns true if the innerCt scroll is already at its left-most point
33878      * @return {Boolean} True if already at furthest left point
33879      */
33880     atExtremeBefore: function() {
33881         return this.getScrollPosition() === 0;
33882     },
33883
33884     /**
33885      * @private
33886      * Scrolls to the left by the configured amount
33887      */
33888     scrollLeft: function() {
33889         this.scrollBy(-this.scrollIncrement, false);
33890     },
33891
33892     /**
33893      * @private
33894      * Scrolls to the right by the configured amount
33895      */
33896     scrollRight: function() {
33897         this.scrollBy(this.scrollIncrement, false);
33898     },
33899
33900     /**
33901      * Returns the current scroll position of the innerCt element
33902      * @return {Number} The current scroll position
33903      */
33904     getScrollPosition: function(){
33905         var layout = this.layout;
33906         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
33907     },
33908
33909     /**
33910      * @private
33911      * Returns the maximum value we can scrollTo
33912      * @return {Number} The max scroll value
33913      */
33914     getMaxScrollPosition: function() {
33915         var layout = this.layout;
33916         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
33917     },
33918
33919     /**
33920      * @private
33921      * Returns true if the innerCt scroll is already at its right-most point
33922      * @return {Boolean} True if already at furthest right point
33923      */
33924     atExtremeAfter: function() {
33925         return this.getScrollPosition() >= this.getMaxScrollPosition();
33926     },
33927
33928     /**
33929      * @private
33930      * Scrolls to the given position. Performs bounds checking.
33931      * @param {Number} position The position to scroll to. This is constrained.
33932      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
33933      */
33934     scrollTo: function(position, animate) {
33935         var me = this,
33936             layout = me.layout,
33937             oldPosition = me.getScrollPosition(),
33938             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
33939
33940         if (newPosition != oldPosition && !me.scrolling) {
33941             if (animate == undefined) {
33942                 animate = me.animateScroll;
33943             }
33944
33945             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
33946             if (animate) {
33947                 me.scrolling = true;
33948             } else {
33949                 me.scrolling = false;
33950                 me.updateScrollButtons();
33951             }
33952             
33953             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
33954         }
33955     },
33956
33957     /**
33958      * Scrolls to the given component.
33959      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
33960      * or a reference to the component itself.
33961      * @param {Boolean} animate True to animate the scrolling
33962      */
33963     scrollToItem: function(item, animate) {
33964         var me = this,
33965             layout = me.layout,
33966             visibility,
33967             box,
33968             newPos;
33969
33970         item = me.getItem(item);
33971         if (item != undefined) {
33972             visibility = this.getItemVisibility(item);
33973             if (!visibility.fullyVisible) {
33974                 box  = item.getBox(true, true);
33975                 newPos = box[layout.parallelPosition];
33976                 if (visibility.hiddenEnd) {
33977                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
33978                 }
33979                 this.scrollTo(newPos, animate);
33980             }
33981         }
33982     },
33983
33984     /**
33985      * @private
33986      * For a given item in the container, return an object with information on whether the item is visible
33987      * with the current innerCt scroll value.
33988      * @param {Ext.Component} item The item
33989      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
33990      */
33991     getItemVisibility: function(item) {
33992         var me          = this,
33993             box         = me.getItem(item).getBox(true, true),
33994             layout      = me.layout,
33995             itemStart   = box[layout.parallelPosition],
33996             itemEnd     = itemStart + box[layout.parallelPrefix],
33997             scrollStart = me.getScrollPosition(),
33998             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
33999
34000         return {
34001             hiddenStart : itemStart < scrollStart,
34002             hiddenEnd   : itemEnd > scrollEnd,
34003             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
34004         };
34005     }
34006 });
34007 /**
34008  * @class Ext.util.Offset
34009  * @ignore
34010  */
34011 Ext.define('Ext.util.Offset', {
34012
34013     /* Begin Definitions */
34014
34015     statics: {
34016         fromObject: function(obj) {
34017             return new this(obj.x, obj.y);
34018         }
34019     },
34020
34021     /* End Definitions */
34022
34023     constructor: function(x, y) {
34024         this.x = (x != null && !isNaN(x)) ? x : 0;
34025         this.y = (y != null && !isNaN(y)) ? y : 0;
34026
34027         return this;
34028     },
34029
34030     copy: function() {
34031         return new Ext.util.Offset(this.x, this.y);
34032     },
34033
34034     copyFrom: function(p) {
34035         this.x = p.x;
34036         this.y = p.y;
34037     },
34038
34039     toString: function() {
34040         return "Offset[" + this.x + "," + this.y + "]";
34041     },
34042
34043     equals: function(offset) {
34044
34045         return (this.x == offset.x && this.y == offset.y);
34046     },
34047
34048     round: function(to) {
34049         if (!isNaN(to)) {
34050             var factor = Math.pow(10, to);
34051             this.x = Math.round(this.x * factor) / factor;
34052             this.y = Math.round(this.y * factor) / factor;
34053         } else {
34054             this.x = Math.round(this.x);
34055             this.y = Math.round(this.y);
34056         }
34057     },
34058
34059     isZero: function() {
34060         return this.x == 0 && this.y == 0;
34061     }
34062 });
34063
34064 /**
34065  * @class Ext.util.KeyNav
34066  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
34067  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
34068  * way to implement custom navigation schemes for any UI component.</p>
34069  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
34070  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
34071  <pre><code>
34072 var nav = new Ext.util.KeyNav("my-element", {
34073     "left" : function(e){
34074         this.moveLeft(e.ctrlKey);
34075     },
34076     "right" : function(e){
34077         this.moveRight(e.ctrlKey);
34078     },
34079     "enter" : function(e){
34080         this.save();
34081     },
34082     scope : this
34083 });
34084 </code></pre>
34085  */
34086 Ext.define('Ext.util.KeyNav', {
34087     
34088     alternateClassName: 'Ext.KeyNav',
34089     
34090     requires: ['Ext.util.KeyMap'],
34091     
34092     statics: {
34093         keyOptions: {
34094             left: 37,
34095             right: 39,
34096             up: 38,
34097             down: 40,
34098             space: 32,
34099             pageUp: 33,
34100             pageDown: 34,
34101             del: 46,
34102             backspace: 8,
34103             home: 36,
34104             end: 35,
34105             enter: 13,
34106             esc: 27,
34107             tab: 9
34108         }
34109     },
34110
34111     /**
34112      * Creates new KeyNav.
34113      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34114      * @param {Object} config The config
34115      */
34116     constructor: function(el, config){
34117         this.setConfig(el, config || {});
34118     },
34119     
34120     /**
34121      * Sets up a configuration for the KeyNav.
34122      * @private
34123      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34124      * @param {Object} config A configuration object as specified in the constructor.
34125      */
34126     setConfig: function(el, config) {
34127         if (this.map) {
34128             this.map.destroy();
34129         }
34130         
34131         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
34132             keys = Ext.util.KeyNav.keyOptions,
34133             scope = config.scope || this,
34134             key;
34135         
34136         this.map = map;
34137         for (key in keys) {
34138             if (keys.hasOwnProperty(key)) {
34139                 if (config[key]) {
34140                     map.addBinding({
34141                         scope: scope,
34142                         key: keys[key],
34143                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
34144                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
34145                     });
34146                 }
34147             }
34148         }
34149         
34150         map.disable();
34151         if (!config.disabled) {
34152             map.enable();
34153         }
34154     },
34155     
34156     /**
34157      * Method for filtering out the map argument
34158      * @private
34159      * @param {Ext.util.KeyMap} map
34160      * @param {Ext.EventObject} event
34161      * @param {Object} options Contains the handler to call
34162      */
34163     handleEvent: function(map, event, handler){
34164         return handler.call(this, event);
34165     },
34166     
34167     /**
34168      * @cfg {Boolean} disabled
34169      * True to disable this KeyNav instance.
34170      */
34171     disabled: false,
34172     
34173     /**
34174      * @cfg {String} defaultEventAction
34175      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
34176      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
34177      * {@link Ext.EventObject#stopPropagation}.
34178      */
34179     defaultEventAction: "stopEvent",
34180     
34181     /**
34182      * @cfg {Boolean} forceKeyDown
34183      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
34184      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34185      * handle keydown instead of keypress.
34186      */
34187     forceKeyDown: false,
34188     
34189     /**
34190      * Destroy this KeyNav (this is the same as calling disable).
34191      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34192      */
34193     destroy: function(removeEl){
34194         this.map.destroy(removeEl);
34195         delete this.map;
34196     },
34197
34198     /**
34199      * Enable this KeyNav
34200      */
34201     enable: function() {
34202         this.map.enable();
34203         this.disabled = false;
34204     },
34205
34206     /**
34207      * Disable this KeyNav
34208      */
34209     disable: function() {
34210         this.map.disable();
34211         this.disabled = true;
34212     },
34213     
34214     /**
34215      * Convenience function for setting disabled/enabled by boolean.
34216      * @param {Boolean} disabled
34217      */
34218     setDisabled : function(disabled){
34219         this.map.setDisabled(disabled);
34220         this.disabled = disabled;
34221     },
34222     
34223     /**
34224      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34225      * as well as the useKeyDown option on the EventManager.
34226      * @return {String} The type of event to listen for.
34227      */
34228     getKeyEvent: function(forceKeyDown){
34229         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34230     }
34231 });
34232
34233 /**
34234  * @class Ext.fx.Queue
34235  * Animation Queue mixin to handle chaining and queueing by target.
34236  * @private
34237  */
34238
34239 Ext.define('Ext.fx.Queue', {
34240
34241     requires: ['Ext.util.HashMap'],
34242
34243     constructor: function() {
34244         this.targets = Ext.create('Ext.util.HashMap');
34245         this.fxQueue = {};
34246     },
34247
34248     // @private
34249     getFxDefaults: function(targetId) {
34250         var target = this.targets.get(targetId);
34251         if (target) {
34252             return target.fxDefaults;
34253         }
34254         return {};
34255     },
34256
34257     // @private
34258     setFxDefaults: function(targetId, obj) {
34259         var target = this.targets.get(targetId);
34260         if (target) {
34261             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34262         }
34263     },
34264
34265     // @private
34266     stopAnimation: function(targetId) {
34267         var me = this,
34268             queue = me.getFxQueue(targetId),
34269             ln = queue.length;
34270         while (ln) {
34271             queue[ln - 1].end();
34272             ln--;
34273         }
34274     },
34275
34276     /**
34277      * @private
34278      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34279      */
34280     getActiveAnimation: function(targetId) {
34281         var queue = this.getFxQueue(targetId);
34282         return (queue && !!queue.length) ? queue[0] : false;
34283     },
34284
34285     // @private
34286     hasFxBlock: function(targetId) {
34287         var queue = this.getFxQueue(targetId);
34288         return queue && queue[0] && queue[0].block;
34289     },
34290
34291     // @private get fx queue for passed target, create if needed.
34292     getFxQueue: function(targetId) {
34293         if (!targetId) {
34294             return false;
34295         }
34296         var me = this,
34297             queue = me.fxQueue[targetId],
34298             target = me.targets.get(targetId);
34299
34300         if (!target) {
34301             return false;
34302         }
34303
34304         if (!queue) {
34305             me.fxQueue[targetId] = [];
34306             // GarbageCollector will need to clean up Elements since they aren't currently observable
34307             if (target.type != 'element') {
34308                 target.target.on('destroy', function() {
34309                     me.fxQueue[targetId] = [];
34310                 });
34311             }
34312         }
34313         return me.fxQueue[targetId];
34314     },
34315
34316     // @private
34317     queueFx: function(anim) {
34318         var me = this,
34319             target = anim.target,
34320             queue, ln;
34321
34322         if (!target) {
34323             return;
34324         }
34325
34326         queue = me.getFxQueue(target.getId());
34327         ln = queue.length;
34328
34329         if (ln) {
34330             if (anim.concurrent) {
34331                 anim.paused = false;
34332             }
34333             else {
34334                 queue[ln - 1].on('afteranimate', function() {
34335                     anim.paused = false;
34336                 });
34337             }
34338         }
34339         else {
34340             anim.paused = false;
34341         }
34342         anim.on('afteranimate', function() {
34343             Ext.Array.remove(queue, anim);
34344             if (anim.remove) {
34345                 if (target.type == 'element') {
34346                     var el = Ext.get(target.id);
34347                     if (el) {
34348                         el.remove();
34349                     }
34350                 }
34351             }
34352         }, this);
34353         queue.push(anim);
34354     }
34355 });
34356 /**
34357  * @class Ext.fx.target.Target
34358
34359 This class specifies a generic target for an animation. It provides a wrapper around a
34360 series of different types of objects to allow for a generic animation API.
34361 A target can be a single object or a Composite object containing other objects that are 
34362 to be animated. This class and it's subclasses are generally not created directly, the 
34363 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34364 the instance to be animated.
34365
34366 The following types of objects can be animated:
34367
34368 - {@link Ext.fx.target.Component Components}
34369 - {@link Ext.fx.target.Element Elements}
34370 - {@link Ext.fx.target.Sprite Sprites}
34371
34372  * @markdown
34373  * @abstract
34374  */
34375 Ext.define('Ext.fx.target.Target', {
34376
34377     isAnimTarget: true,
34378
34379     /**
34380      * Creates new Target.
34381      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
34382      */
34383     constructor: function(target) {
34384         this.target = target;
34385         this.id = this.getId();
34386     },
34387     
34388     getId: function() {
34389         return this.target.id;
34390     }
34391 });
34392
34393 /**
34394  * @class Ext.fx.target.Sprite
34395  * @extends Ext.fx.target.Target
34396
34397 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
34398 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
34399 and the appropriate target will be created.
34400
34401  * @markdown
34402  */
34403
34404 Ext.define('Ext.fx.target.Sprite', {
34405
34406     /* Begin Definitions */
34407
34408     extend: 'Ext.fx.target.Target',
34409
34410     /* End Definitions */
34411
34412     type: 'draw',
34413
34414     getFromPrim: function(sprite, attr) {
34415         var o;
34416         if (attr == 'translate') {
34417             o = {
34418                 x: sprite.attr.translation.x || 0,
34419                 y: sprite.attr.translation.y || 0
34420             };
34421         }
34422         else if (attr == 'rotate') {
34423             o = {
34424                 degrees: sprite.attr.rotation.degrees || 0,
34425                 x: sprite.attr.rotation.x,
34426                 y: sprite.attr.rotation.y
34427             };
34428         }
34429         else {
34430             o = sprite.attr[attr];
34431         }
34432         return o;
34433     },
34434
34435     getAttr: function(attr, val) {
34436         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
34437     },
34438
34439     setAttr: function(targetData) {
34440         var ln = targetData.length,
34441             spriteArr = [],
34442             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
34443         for (i = 0; i < ln; i++) {
34444             attrs = targetData[i].attrs;
34445             for (attr in attrs) {
34446                 attrArr = attrs[attr];
34447                 ln2 = attrArr.length;
34448                 for (j = 0; j < ln2; j++) {
34449                     spritePtr = attrArr[j][0];
34450                     attPtr = attrArr[j][1];
34451                     if (attr === 'translate') {
34452                         value = {
34453                             x: attPtr.x,
34454                             y: attPtr.y
34455                         };
34456                     }
34457                     else if (attr === 'rotate') {
34458                         x = attPtr.x;
34459                         if (isNaN(x)) {
34460                             x = null;
34461                         }
34462                         y = attPtr.y;
34463                         if (isNaN(y)) {
34464                             y = null;
34465                         }
34466                         value = {
34467                             degrees: attPtr.degrees,
34468                             x: x,
34469                             y: y
34470                         };
34471                     }
34472                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
34473                         value = parseFloat(attPtr);
34474                     }
34475                     else {
34476                         value = attPtr;
34477                     }
34478                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
34479                     if (idx == -1) {
34480                         spriteArr.push([spritePtr, {}]);
34481                         idx = spriteArr.length - 1;
34482                     }
34483                     spriteArr[idx][1][attr] = value;
34484                 }
34485             }
34486         }
34487         ln = spriteArr.length;
34488         for (i = 0; i < ln; i++) {
34489             spritePtr = spriteArr[i];
34490             spritePtr[0].setAttributes(spritePtr[1]);
34491         }
34492         this.target.redraw();
34493     }
34494 });
34495
34496 /**
34497  * @class Ext.fx.target.CompositeSprite
34498  * @extends Ext.fx.target.Sprite
34499
34500 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
34501 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
34502 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
34503 and the appropriate target will be created.
34504
34505  * @markdown
34506  */
34507
34508 Ext.define('Ext.fx.target.CompositeSprite', {
34509
34510     /* Begin Definitions */
34511
34512     extend: 'Ext.fx.target.Sprite',
34513
34514     /* End Definitions */
34515
34516     getAttr: function(attr, val) {
34517         var out = [],
34518             target = this.target;
34519         target.each(function(sprite) {
34520             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
34521         }, this);
34522         return out;
34523     }
34524 });
34525
34526 /**
34527  * @class Ext.fx.target.Component
34528  * @extends Ext.fx.target.Target
34529  * 
34530  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
34531  * created directly, the {@link Ext.Component} will be passed to the animation and
34532  * and the appropriate target will be created.
34533  */
34534 Ext.define('Ext.fx.target.Component', {
34535
34536     /* Begin Definitions */
34537    
34538     extend: 'Ext.fx.target.Target',
34539     
34540     /* End Definitions */
34541
34542     type: 'component',
34543
34544     // Methods to call to retrieve unspecified "from" values from a target Component
34545     getPropMethod: {
34546         top: function() {
34547             return this.getPosition(true)[1];
34548         },
34549         left: function() {
34550             return this.getPosition(true)[0];
34551         },
34552         x: function() {
34553             return this.getPosition()[0];
34554         },
34555         y: function() {
34556             return this.getPosition()[1];
34557         },
34558         height: function() {
34559             return this.getHeight();
34560         },
34561         width: function() {
34562             return this.getWidth();
34563         },
34564         opacity: function() {
34565             return this.el.getStyle('opacity');
34566         }
34567     },
34568
34569     compMethod: {
34570         top: 'setPosition',
34571         left: 'setPosition',
34572         x: 'setPagePosition',
34573         y: 'setPagePosition',
34574         height: 'setSize',
34575         width: 'setSize',
34576         opacity: 'setOpacity'
34577     },
34578
34579     // Read the named attribute from the target Component. Use the defined getter for the attribute
34580     getAttr: function(attr, val) {
34581         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
34582     },
34583
34584     setAttr: function(targetData, isFirstFrame, isLastFrame) {
34585         var me = this,
34586             target = me.target,
34587             ln = targetData.length,
34588             attrs, attr, o, i, j, meth, targets, left, top, w, h;
34589         for (i = 0; i < ln; i++) {
34590             attrs = targetData[i].attrs;
34591             for (attr in attrs) {
34592                 targets = attrs[attr].length;
34593                 meth = {
34594                     setPosition: {},
34595                     setPagePosition: {},
34596                     setSize: {},
34597                     setOpacity: {}
34598                 };
34599                 for (j = 0; j < targets; j++) {
34600                     o = attrs[attr][j];
34601                     // We REALLY want a single function call, so push these down to merge them: eg
34602                     // meth.setPagePosition.target = <targetComponent>
34603                     // meth.setPagePosition['x'] = 100
34604                     // meth.setPagePosition['y'] = 100
34605                     meth[me.compMethod[attr]].target = o[0];
34606                     meth[me.compMethod[attr]][attr] = o[1];
34607                 }
34608                 if (meth.setPosition.target) {
34609                     o = meth.setPosition;
34610                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
34611                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
34612                     o.target.setPosition(left, top);
34613                 }
34614                 if (meth.setPagePosition.target) {
34615                     o = meth.setPagePosition;
34616                     o.target.setPagePosition(o.x, o.y);
34617                 }
34618                 if (meth.setSize.target && meth.setSize.target.el) {
34619                     o = meth.setSize;
34620                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
34621                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
34622                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
34623
34624                     // Only set the size of the Component on the last frame, or if the animation was
34625                     // configured with dynamic: true.
34626                     // In other cases, we just set the target element size.
34627                     // This will result in either clipping if animating a reduction in size, or the revealing of
34628                     // the inner elements of the Component if animating an increase in size.
34629                     // Component's animate function initially resizes to the larger size before resizing the
34630                     // outer element to clip the contents.
34631                     if (isLastFrame || me.dynamic) {
34632                         o.target.componentLayout.childrenChanged = true;
34633
34634                         // Flag if we are being called by an animating layout: use setCalculatedSize
34635                         if (me.layoutAnimation) {
34636                             o.target.setCalculatedSize(w, h);
34637                         } else {
34638                             o.target.setSize(w, h);
34639                         }
34640                     }
34641                     else {
34642                         o.target.el.setSize(w, h);
34643                     }
34644                 }
34645                 if (meth.setOpacity.target) {
34646                     o = meth.setOpacity;
34647                     o.target.el.setStyle('opacity', o.opacity);
34648                 }
34649             }
34650         }
34651     }
34652 });
34653
34654 /**
34655  * @class Ext.fx.CubicBezier
34656  * @ignore
34657  */
34658 Ext.define('Ext.fx.CubicBezier', {
34659
34660     /* Begin Definitions */
34661
34662     singleton: true,
34663
34664     /* End Definitions */
34665
34666     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
34667         var cx = 3 * p1x,
34668             bx = 3 * (p2x - p1x) - cx,
34669             ax = 1 - cx - bx,
34670             cy = 3 * p1y,
34671             by = 3 * (p2y - p1y) - cy,
34672             ay = 1 - cy - by;
34673         function sampleCurveX(t) {
34674             return ((ax * t + bx) * t + cx) * t;
34675         }
34676         function solve(x, epsilon) {
34677             var t = solveCurveX(x, epsilon);
34678             return ((ay * t + by) * t + cy) * t;
34679         }
34680         function solveCurveX(x, epsilon) {
34681             var t0, t1, t2, x2, d2, i;
34682             for (t2 = x, i = 0; i < 8; i++) {
34683                 x2 = sampleCurveX(t2) - x;
34684                 if (Math.abs(x2) < epsilon) {
34685                     return t2;
34686                 }
34687                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
34688                 if (Math.abs(d2) < 1e-6) {
34689                     break;
34690                 }
34691                 t2 = t2 - x2 / d2;
34692             }
34693             t0 = 0;
34694             t1 = 1;
34695             t2 = x;
34696             if (t2 < t0) {
34697                 return t0;
34698             }
34699             if (t2 > t1) {
34700                 return t1;
34701             }
34702             while (t0 < t1) {
34703                 x2 = sampleCurveX(t2);
34704                 if (Math.abs(x2 - x) < epsilon) {
34705                     return t2;
34706                 }
34707                 if (x > x2) {
34708                     t0 = t2;
34709                 } else {
34710                     t1 = t2;
34711                 }
34712                 t2 = (t1 - t0) / 2 + t0;
34713             }
34714             return t2;
34715         }
34716         return solve(t, 1 / (200 * duration));
34717     },
34718
34719     cubicBezier: function(x1, y1, x2, y2) {
34720         var fn = function(pos) {
34721             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
34722         };
34723         fn.toCSS3 = function() {
34724             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
34725         };
34726         fn.reverse = function() {
34727             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
34728         };
34729         return fn;
34730     }
34731 });
34732 /**
34733  * Represents an RGB color and provides helper functions get
34734  * color components in HSL color space.
34735  */
34736 Ext.define('Ext.draw.Color', {
34737
34738     /* Begin Definitions */
34739
34740     /* End Definitions */
34741
34742     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
34743     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
34744     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*/,
34745
34746     /**
34747      * @cfg {Number} lightnessFactor
34748      *
34749      * The default factor to compute the lighter or darker color. Defaults to 0.2.
34750      */
34751     lightnessFactor: 0.2,
34752
34753     /**
34754      * Creates new Color.
34755      * @param {Number} red Red component (0..255)
34756      * @param {Number} green Green component (0..255)
34757      * @param {Number} blue Blue component (0..255)
34758      */
34759     constructor : function(red, green, blue) {
34760         var me = this,
34761             clamp = Ext.Number.constrain;
34762         me.r = clamp(red, 0, 255);
34763         me.g = clamp(green, 0, 255);
34764         me.b = clamp(blue, 0, 255);
34765     },
34766
34767     /**
34768      * Get the red component of the color, in the range 0..255.
34769      * @return {Number}
34770      */
34771     getRed: function() {
34772         return this.r;
34773     },
34774
34775     /**
34776      * Get the green component of the color, in the range 0..255.
34777      * @return {Number}
34778      */
34779     getGreen: function() {
34780         return this.g;
34781     },
34782
34783     /**
34784      * Get the blue component of the color, in the range 0..255.
34785      * @return {Number}
34786      */
34787     getBlue: function() {
34788         return this.b;
34789     },
34790
34791     /**
34792      * Get the RGB values.
34793      * @return {Number[]}
34794      */
34795     getRGB: function() {
34796         var me = this;
34797         return [me.r, me.g, me.b];
34798     },
34799
34800     /**
34801      * Get the equivalent HSL components of the color.
34802      * @return {Number[]}
34803      */
34804     getHSL: function() {
34805         var me = this,
34806             r = me.r / 255,
34807             g = me.g / 255,
34808             b = me.b / 255,
34809             max = Math.max(r, g, b),
34810             min = Math.min(r, g, b),
34811             delta = max - min,
34812             h,
34813             s = 0,
34814             l = 0.5 * (max + min);
34815
34816         // min==max means achromatic (hue is undefined)
34817         if (min != max) {
34818             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
34819             if (r == max) {
34820                 h = 60 * (g - b) / delta;
34821             } else if (g == max) {
34822                 h = 120 + 60 * (b - r) / delta;
34823             } else {
34824                 h = 240 + 60 * (r - g) / delta;
34825             }
34826             if (h < 0) {
34827                 h += 360;
34828             }
34829             if (h >= 360) {
34830                 h -= 360;
34831             }
34832         }
34833         return [h, s, l];
34834     },
34835
34836     /**
34837      * Return a new color that is lighter than this color.
34838      * @param {Number} factor Lighter factor (0..1), default to 0.2
34839      * @return Ext.draw.Color
34840      */
34841     getLighter: function(factor) {
34842         var hsl = this.getHSL();
34843         factor = factor || this.lightnessFactor;
34844         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
34845         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
34846     },
34847
34848     /**
34849      * Return a new color that is darker than this color.
34850      * @param {Number} factor Darker factor (0..1), default to 0.2
34851      * @return Ext.draw.Color
34852      */
34853     getDarker: function(factor) {
34854         factor = factor || this.lightnessFactor;
34855         return this.getLighter(-factor);
34856     },
34857
34858     /**
34859      * Return the color in the hex format, i.e. '#rrggbb'.
34860      * @return {String}
34861      */
34862     toString: function() {
34863         var me = this,
34864             round = Math.round,
34865             r = round(me.r).toString(16),
34866             g = round(me.g).toString(16),
34867             b = round(me.b).toString(16);
34868         r = (r.length == 1) ? '0' + r : r;
34869         g = (g.length == 1) ? '0' + g : g;
34870         b = (b.length == 1) ? '0' + b : b;
34871         return ['#', r, g, b].join('');
34872     },
34873
34874     /**
34875      * Convert a color to hexadecimal format.
34876      *
34877      * **Note:** This method is both static and instance.
34878      *
34879      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
34880      * Can also be an Array, in this case the function handles the first member.
34881      * @returns {String} The color in hexadecimal format.
34882      * @static
34883      */
34884     toHex: function(color) {
34885         if (Ext.isArray(color)) {
34886             color = color[0];
34887         }
34888         if (!Ext.isString(color)) {
34889             return '';
34890         }
34891         if (color.substr(0, 1) === '#') {
34892             return color;
34893         }
34894         var digits = this.colorToHexRe.exec(color);
34895
34896         if (Ext.isArray(digits)) {
34897             var red = parseInt(digits[2], 10),
34898                 green = parseInt(digits[3], 10),
34899                 blue = parseInt(digits[4], 10),
34900                 rgb = blue | (green << 8) | (red << 16);
34901             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
34902         }
34903         else {
34904             return '';
34905         }
34906     },
34907
34908     /**
34909      * Parse the string and create a new color.
34910      *
34911      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
34912      *
34913      * If the string is not recognized, an undefined will be returned instead.
34914      *
34915      * **Note:** This method is both static and instance.
34916      *
34917      * @param {String} str Color in string.
34918      * @returns Ext.draw.Color
34919      * @static
34920      */
34921     fromString: function(str) {
34922         var values, r, g, b,
34923             parse = parseInt;
34924
34925         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
34926             values = str.match(this.hexRe);
34927             if (values) {
34928                 r = parse(values[1], 16) >> 0;
34929                 g = parse(values[2], 16) >> 0;
34930                 b = parse(values[3], 16) >> 0;
34931                 if (str.length == 4) {
34932                     r += (r * 16);
34933                     g += (g * 16);
34934                     b += (b * 16);
34935                 }
34936             }
34937         }
34938         else {
34939             values = str.match(this.rgbRe);
34940             if (values) {
34941                 r = values[1];
34942                 g = values[2];
34943                 b = values[3];
34944             }
34945         }
34946
34947         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
34948     },
34949
34950     /**
34951      * Returns the gray value (0 to 255) of the color.
34952      *
34953      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
34954      *
34955      * @returns {Number}
34956      */
34957     getGrayscale: function() {
34958         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
34959         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
34960     },
34961
34962     /**
34963      * Create a new color based on the specified HSL values.
34964      *
34965      * **Note:** This method is both static and instance.
34966      *
34967      * @param {Number} h Hue component (0..359)
34968      * @param {Number} s Saturation component (0..1)
34969      * @param {Number} l Lightness component (0..1)
34970      * @returns Ext.draw.Color
34971      * @static
34972      */
34973     fromHSL: function(h, s, l) {
34974         var C, X, m, i, rgb = [],
34975             abs = Math.abs,
34976             floor = Math.floor;
34977
34978         if (s == 0 || h == null) {
34979             // achromatic
34980             rgb = [l, l, l];
34981         }
34982         else {
34983             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
34984             // C is the chroma
34985             // X is the second largest component
34986             // m is the lightness adjustment
34987             h /= 60;
34988             C = s * (1 - abs(2 * l - 1));
34989             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
34990             m = l - C / 2;
34991             switch (floor(h)) {
34992                 case 0:
34993                     rgb = [C, X, 0];
34994                     break;
34995                 case 1:
34996                     rgb = [X, C, 0];
34997                     break;
34998                 case 2:
34999                     rgb = [0, C, X];
35000                     break;
35001                 case 3:
35002                     rgb = [0, X, C];
35003                     break;
35004                 case 4:
35005                     rgb = [X, 0, C];
35006                     break;
35007                 case 5:
35008                     rgb = [C, 0, X];
35009                     break;
35010             }
35011             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
35012         }
35013         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
35014     }
35015 }, function() {
35016     var prototype = this.prototype;
35017
35018     //These functions are both static and instance. TODO: find a more elegant way of copying them
35019     this.addStatics({
35020         fromHSL: function() {
35021             return prototype.fromHSL.apply(prototype, arguments);
35022         },
35023         fromString: function() {
35024             return prototype.fromString.apply(prototype, arguments);
35025         },
35026         toHex: function() {
35027             return prototype.toHex.apply(prototype, arguments);
35028         }
35029     });
35030 });
35031
35032 /**
35033  * @class Ext.dd.StatusProxy
35034  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
35035  * default drag proxy used by all Ext.dd components.
35036  */
35037 Ext.define('Ext.dd.StatusProxy', {
35038     animRepair: false,
35039
35040     /**
35041      * Creates new StatusProxy.
35042      * @param {Object} config (optional) Config object.
35043      */
35044     constructor: function(config){
35045         Ext.apply(this, config);
35046         this.id = this.id || Ext.id();
35047         this.proxy = Ext.createWidget('component', {
35048             floating: true,
35049             stateful: false,
35050             id: this.id,
35051             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
35052                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
35053             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
35054             shadow: !config || config.shadow !== false,
35055             renderTo: document.body
35056         });
35057
35058         this.el = this.proxy.el;
35059         this.el.show();
35060         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
35061         this.el.hide();
35062
35063         this.ghost = Ext.get(this.el.dom.childNodes[1]);
35064         this.dropStatus = this.dropNotAllowed;
35065     },
35066     /**
35067      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
35068      * The CSS class to apply to the status element when drop is allowed.
35069      */
35070     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
35071     /**
35072      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
35073      * The CSS class to apply to the status element when drop is not allowed.
35074      */
35075     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
35076
35077     /**
35078      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
35079      * over the current target element.
35080      * @param {String} cssClass The css class for the new drop status indicator image
35081      */
35082     setStatus : function(cssClass){
35083         cssClass = cssClass || this.dropNotAllowed;
35084         if(this.dropStatus != cssClass){
35085             this.el.replaceCls(this.dropStatus, cssClass);
35086             this.dropStatus = cssClass;
35087         }
35088     },
35089
35090     /**
35091      * Resets the status indicator to the default dropNotAllowed value
35092      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
35093      */
35094     reset : function(clearGhost){
35095         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
35096         this.dropStatus = this.dropNotAllowed;
35097         if(clearGhost){
35098             this.ghost.update("");
35099         }
35100     },
35101
35102     /**
35103      * Updates the contents of the ghost element
35104      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
35105      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
35106      */
35107     update : function(html){
35108         if(typeof html == "string"){
35109             this.ghost.update(html);
35110         }else{
35111             this.ghost.update("");
35112             html.style.margin = "0";
35113             this.ghost.dom.appendChild(html);
35114         }
35115         var el = this.ghost.dom.firstChild;
35116         if(el){
35117             Ext.fly(el).setStyle('float', 'none');
35118         }
35119     },
35120
35121     /**
35122      * Returns the underlying proxy {@link Ext.Layer}
35123      * @return {Ext.Layer} el
35124     */
35125     getEl : function(){
35126         return this.el;
35127     },
35128
35129     /**
35130      * Returns the ghost element
35131      * @return {Ext.Element} el
35132      */
35133     getGhost : function(){
35134         return this.ghost;
35135     },
35136
35137     /**
35138      * Hides the proxy
35139      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
35140      */
35141     hide : function(clear) {
35142         this.proxy.hide();
35143         if (clear) {
35144             this.reset(true);
35145         }
35146     },
35147
35148     /**
35149      * Stops the repair animation if it's currently running
35150      */
35151     stop : function(){
35152         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
35153             this.anim.stop();
35154         }
35155     },
35156
35157     /**
35158      * Displays this proxy
35159      */
35160     show : function() {
35161         this.proxy.show();
35162         this.proxy.toFront();
35163     },
35164
35165     /**
35166      * Force the Layer to sync its shadow and shim positions to the element
35167      */
35168     sync : function(){
35169         this.proxy.el.sync();
35170     },
35171
35172     /**
35173      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
35174      * invalid drop operation by the item being dragged.
35175      * @param {Number[]} xy The XY position of the element ([x, y])
35176      * @param {Function} callback The function to call after the repair is complete.
35177      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
35178      */
35179     repair : function(xy, callback, scope){
35180         this.callback = callback;
35181         this.scope = scope;
35182         if (xy && this.animRepair !== false) {
35183             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
35184             this.el.hideUnders(true);
35185             this.anim = this.el.animate({
35186                 duration: this.repairDuration || 500,
35187                 easing: 'ease-out',
35188                 to: {
35189                     x: xy[0],
35190                     y: xy[1]
35191                 },
35192                 stopAnimation: true,
35193                 callback: this.afterRepair,
35194                 scope: this
35195             });
35196         } else {
35197             this.afterRepair();
35198         }
35199     },
35200
35201     // private
35202     afterRepair : function(){
35203         this.hide(true);
35204         if(typeof this.callback == "function"){
35205             this.callback.call(this.scope || this);
35206         }
35207         this.callback = null;
35208         this.scope = null;
35209     },
35210
35211     destroy: function(){
35212         Ext.destroy(this.ghost, this.proxy, this.el);
35213     }
35214 });
35215 /**
35216  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35217  * is primarily used internally for the Panel's drag drop implementation, and
35218  * should never need to be created directly.
35219  * @private
35220  */
35221 Ext.define('Ext.panel.Proxy', {
35222
35223     alternateClassName: 'Ext.dd.PanelProxy',
35224
35225     /**
35226      * Creates new panel proxy.
35227      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
35228      * @param {Object} [config] Config object
35229      */
35230     constructor: function(panel, config){
35231         /**
35232          * @property panel
35233          * @type Ext.panel.Panel
35234          */
35235         this.panel = panel;
35236         this.id = this.panel.id +'-ddproxy';
35237         Ext.apply(this, config);
35238     },
35239
35240     /**
35241      * @cfg {Boolean} insertProxy
35242      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
35243      * Most Panels are not absolute positioned and therefore we need to reserve this space.
35244      */
35245     insertProxy: true,
35246
35247     // private overrides
35248     setStatus: Ext.emptyFn,
35249     reset: Ext.emptyFn,
35250     update: Ext.emptyFn,
35251     stop: Ext.emptyFn,
35252     sync: Ext.emptyFn,
35253
35254     /**
35255      * Gets the proxy's element
35256      * @return {Ext.Element} The proxy's element
35257      */
35258     getEl: function(){
35259         return this.ghost.el;
35260     },
35261
35262     /**
35263      * Gets the proxy's ghost Panel
35264      * @return {Ext.panel.Panel} The proxy's ghost Panel
35265      */
35266     getGhost: function(){
35267         return this.ghost;
35268     },
35269
35270     /**
35271      * Gets the proxy element. This is the element that represents where the
35272      * Panel was before we started the drag operation.
35273      * @return {Ext.Element} The proxy's element
35274      */
35275     getProxy: function(){
35276         return this.proxy;
35277     },
35278
35279     /**
35280      * Hides the proxy
35281      */
35282     hide : function(){
35283         if (this.ghost) {
35284             if (this.proxy) {
35285                 this.proxy.remove();
35286                 delete this.proxy;
35287             }
35288
35289             // Unghost the Panel, do not move the Panel to where the ghost was
35290             this.panel.unghost(null, false);
35291             delete this.ghost;
35292         }
35293     },
35294
35295     /**
35296      * Shows the proxy
35297      */
35298     show: function(){
35299         if (!this.ghost) {
35300             var panelSize = this.panel.getSize();
35301             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
35302             this.ghost = this.panel.ghost();
35303             if (this.insertProxy) {
35304                 // bc Panels aren't absolute positioned we need to take up the space
35305                 // of where the panel previously was
35306                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35307                 this.proxy.setSize(panelSize);
35308             }
35309         }
35310     },
35311
35312     // private
35313     repair: function(xy, callback, scope) {
35314         this.hide();
35315         if (typeof callback == "function") {
35316             callback.call(scope || this);
35317         }
35318     },
35319
35320     /**
35321      * Moves the proxy to a different position in the DOM.  This is typically
35322      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35323      * location.
35324      * @param {HTMLElement} parentNode The proxy's parent DOM node
35325      * @param {HTMLElement} [before] The sibling node before which the
35326      * proxy should be inserted (defaults to the parent's last child if not
35327      * specified)
35328      */
35329     moveProxy : function(parentNode, before){
35330         if (this.proxy) {
35331             parentNode.insertBefore(this.proxy.dom, before);
35332         }
35333     }
35334 });
35335 /**
35336  * @class Ext.layout.component.AbstractDock
35337  * @extends Ext.layout.component.Component
35338  * @private
35339  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35340  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35341  * an AutoContainerLayout in which this panel get his height based of the CSS or
35342  * or its content.
35343  */
35344
35345 Ext.define('Ext.layout.component.AbstractDock', {
35346
35347     /* Begin Definitions */
35348
35349     extend: 'Ext.layout.component.Component',
35350
35351     /* End Definitions */
35352
35353     type: 'dock',
35354
35355     /**
35356      * @private
35357      * @property autoSizing
35358      * @type Boolean
35359      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35360      */
35361     autoSizing: true,
35362
35363     beforeLayout: function() {
35364         var returnValue = this.callParent(arguments);
35365         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35366             this.handleItemBorders();
35367             this.initializedBorders = true;
35368         }
35369         return returnValue;
35370     },
35371     
35372     handleItemBorders: function() {
35373         var owner = this.owner,
35374             body = owner.body,
35375             docked = this.getLayoutItems(),
35376             borders = {
35377                 top: [],
35378                 right: [],
35379                 bottom: [],
35380                 left: []
35381             },
35382             oldBorders = this.borders,
35383             opposites = {
35384                 top: 'bottom',
35385                 right: 'left',
35386                 bottom: 'top',
35387                 left: 'right'
35388             },
35389             i, ln, item, dock, side;
35390
35391         for (i = 0, ln = docked.length; i < ln; i++) {
35392             item = docked[i];
35393             dock = item.dock;
35394             
35395             if (item.ignoreBorderManagement) {
35396                 continue;
35397             }
35398             
35399             if (!borders[dock].satisfied) {
35400                 borders[dock].push(item);
35401                 borders[dock].satisfied = true;
35402             }
35403             
35404             if (!borders.top.satisfied && opposites[dock] !== 'top') {
35405                 borders.top.push(item);
35406             }
35407             if (!borders.right.satisfied && opposites[dock] !== 'right') {
35408                 borders.right.push(item);
35409             }            
35410             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
35411                 borders.bottom.push(item);
35412             }            
35413             if (!borders.left.satisfied && opposites[dock] !== 'left') {
35414                 borders.left.push(item);
35415             }
35416         }
35417
35418         if (oldBorders) {
35419             for (side in oldBorders) {
35420                 if (oldBorders.hasOwnProperty(side)) {
35421                     ln = oldBorders[side].length;
35422                     if (!owner.manageBodyBorders) {
35423                         for (i = 0; i < ln; i++) {
35424                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35425                         }
35426                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
35427                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35428                         }                    
35429                     }
35430                     else if (oldBorders[side].satisfied) {
35431                         body.setStyle('border-' + side + '-width', '');
35432                     }
35433                 }
35434             }
35435         }
35436                 
35437         for (side in borders) {
35438             if (borders.hasOwnProperty(side)) {
35439                 ln = borders[side].length;
35440                 if (!owner.manageBodyBorders) {
35441                     for (i = 0; i < ln; i++) {
35442                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35443                     }
35444                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
35445                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35446                     }                    
35447                 }
35448                 else if (borders[side].satisfied) {
35449                     body.setStyle('border-' + side + '-width', '1px');
35450                 }
35451             }
35452         }
35453         
35454         this.borders = borders;
35455     },
35456     
35457     /**
35458      * @protected
35459      * @param {Ext.Component} owner The Panel that owns this DockLayout
35460      * @param {Ext.Element} target The target in which we are going to render the docked items
35461      * @param {Array} args The arguments passed to the ComponentLayout.layout method
35462      */
35463     onLayout: function(width, height) {
35464         if (this.onLayout_running) {
35465             return;
35466         }
35467         this.onLayout_running = true;
35468         var me = this,
35469             owner = me.owner,
35470             body = owner.body,
35471             layout = owner.layout,
35472             target = me.getTarget(),
35473             autoWidth = false,
35474             autoHeight = false,
35475             padding, border, frameSize;
35476
35477         // We start of by resetting all the layouts info
35478         var info = me.info = {
35479             boxes: [],
35480             size: {
35481                 width: width,
35482                 height: height
35483             },
35484             bodyBox: {}
35485         };
35486         // Clear isAutoDock flag
35487         delete layout.isAutoDock;
35488
35489         Ext.applyIf(info, me.getTargetInfo());
35490
35491         // We need to bind to the ownerCt whenever we do not have a user set height or width.
35492         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
35493             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
35494                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
35495             }
35496             else {
35497                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
35498             }
35499         }
35500
35501         // Determine if we have an autoHeight or autoWidth.
35502         if (height == null || width == null) {
35503             padding = info.padding;
35504             border = info.border;
35505             frameSize = me.frameSize;
35506
35507             // Auto-everything, clear out any style height/width and read from css
35508             if ((height == null) && (width == null)) {
35509                 autoHeight = true;
35510                 autoWidth = true;
35511                 me.setTargetSize(null);
35512                 me.setBodyBox({width: null, height: null});
35513             }
35514             // Auto-height
35515             else if (height == null) {
35516                 autoHeight = true;
35517                 // Clear any sizing that we already set in a previous layout
35518                 me.setTargetSize(width);
35519                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
35520             // Auto-width
35521             }
35522             else {
35523                 autoWidth = true;
35524                 // Clear any sizing that we already set in a previous layout
35525                 me.setTargetSize(null, height);
35526                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
35527             }
35528
35529             // Run the container
35530             if (layout && layout.isLayout) {
35531                 // Auto-Sized so have the container layout notify the component layout.
35532                 layout.bindToOwnerCtComponent = true;
35533                 // Set flag so we don't do a redundant container layout
35534                 layout.isAutoDock = layout.autoSize !== true;
35535                 layout.layout();
35536
35537                 // If this is an autosized container layout, then we must compensate for a
35538                 // body that is being autosized.  We do not want to adjust the body's size
35539                 // to accommodate the dock items, but rather we will want to adjust the
35540                 // target's size.
35541                 //
35542                 // This is necessary because, particularly in a Box layout, all child items
35543                 // are set with absolute dimensions that are not flexible to the size of its
35544                 // innerCt/target.  So once they are laid out, they are sized for good. By
35545                 // shrinking the body box to accommodate dock items, we're merely cutting off
35546                 // parts of the body.  Not good.  Instead, the target's size should expand
35547                 // to fit the dock items in.  This is valid because the target container is
35548                 // suppose to be autosized to fit everything accordingly.
35549                 info.autoSizedCtLayout = layout.autoSize === true;
35550                 info.autoHeight = autoHeight;
35551                 info.autoWidth = autoWidth;
35552             }
35553
35554             // The dockItems method will add all the top and bottom docked items height
35555             // to the info.panelSize height. That's why we have to call setSize after
35556             // we dock all the items to actually set the panel's width and height.
35557             // We have to do this because the panel body and docked items will be position
35558             // absolute which doesn't stretch the panel.
35559             me.dockItems();
35560             me.setTargetSize(info.size.width, info.size.height);
35561         }
35562         else {
35563             me.setTargetSize(width, height);
35564             me.dockItems();
35565         }
35566         me.callParent(arguments);
35567         this.onLayout_running = false;
35568     },
35569
35570     /**
35571      * @protected
35572      * This method will first update all the information about the docked items,
35573      * body dimensions and position, the panel's total size. It will then
35574      * set all these values on the docked items and panel body.
35575      * @param {Array} items Array containing all the docked items
35576      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
35577      * AutoContainerLayout
35578      */
35579     dockItems : function() {
35580         this.calculateDockBoxes();
35581
35582         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
35583         // information about the body, panel size, and boxes for docked items
35584         // inside a property called info.
35585         var info = this.info,
35586             autoWidth = info.autoWidth,
35587             autoHeight = info.autoHeight,
35588             boxes = info.boxes,
35589             ln = boxes.length,
35590             dock, i, item;
35591
35592         // We are going to loop over all the boxes that were calculated
35593         // and set the position of each item the box belongs to.
35594         for (i = 0; i < ln; i++) {
35595             dock = boxes[i];
35596             item = dock.item;
35597             item.setPosition(dock.x, dock.y);
35598             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
35599                 // Auto-Sized so have the container layout notify the component layout.
35600                 item.layout.bindToOwnerCtComponent = true;
35601             }
35602         }
35603
35604         // Don't adjust body width/height if the target is using an auto container layout.
35605         // But, we do want to adjust the body size if the container layout is auto sized.
35606         if (!info.autoSizedCtLayout) {
35607             if (autoWidth) {
35608                 info.bodyBox.width = null;
35609             }
35610             if (autoHeight) {
35611                 info.bodyBox.height = null;
35612             }
35613         }
35614
35615         // If the bodyBox has been adjusted because of the docked items
35616         // we will update the dimensions and position of the panel's body.
35617         this.setBodyBox(info.bodyBox);
35618     },
35619
35620     /**
35621      * @protected
35622      * This method will set up some initial information about the panel size and bodybox
35623      * and then loop over all the items you pass it to take care of stretching, aligning,
35624      * dock position and all calculations involved with adjusting the body box.
35625      * @param {Array} items Array containing all the docked items we have to layout
35626      */
35627     calculateDockBoxes : function() {
35628         if (this.calculateDockBoxes_running) {
35629             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
35630             return;
35631         }
35632         this.calculateDockBoxes_running = true;
35633         // We want to use the Panel's el width, and the Panel's body height as the initial
35634         // size we are going to use in calculateDockBoxes. We also want to account for
35635         // the border of the panel.
35636         var me = this,
35637             target = me.getTarget(),
35638             items = me.getLayoutItems(),
35639             owner = me.owner,
35640             bodyEl = owner.body,
35641             info = me.info,
35642             autoWidth = info.autoWidth,
35643             autoHeight = info.autoHeight,
35644             size = info.size,
35645             ln = items.length,
35646             padding = info.padding,
35647             border = info.border,
35648             frameSize = me.frameSize,
35649             item, i, box, rect;
35650
35651         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
35652         // around the height of the body and the width of the panel.
35653         if (autoHeight) {
35654             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
35655         }
35656         else {
35657             size.height = target.getHeight();
35658         }
35659         if (autoWidth) {
35660             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
35661         }
35662         else {
35663             size.width = target.getWidth();
35664         }
35665
35666         info.bodyBox = {
35667             x: padding.left + frameSize.left,
35668             y: padding.top + frameSize.top,
35669             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
35670             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
35671         };
35672
35673         // Loop over all the docked items
35674         for (i = 0; i < ln; i++) {
35675             item = items[i];
35676             // The initBox method will take care of stretching and alignment
35677             // In some cases it will also layout the dock items to be able to
35678             // get a width or height measurement
35679             box = me.initBox(item);
35680
35681             if (autoHeight === true) {
35682                 box = me.adjustAutoBox(box, i);
35683             }
35684             else {
35685                 box = me.adjustSizedBox(box, i);
35686             }
35687
35688             // Save our box. This allows us to loop over all docked items and do all
35689             // calculations first. Then in one loop we will actually size and position
35690             // all the docked items that have changed.
35691             info.boxes.push(box);
35692         }
35693         this.calculateDockBoxes_running = false;
35694     },
35695
35696     /**
35697      * @protected
35698      * This method will adjust the position of the docked item and adjust the body box
35699      * accordingly.
35700      * @param {Object} box The box containing information about the width and height
35701      * of this docked item
35702      * @param {Number} index The index position of this docked item
35703      * @return {Object} The adjusted box
35704      */
35705     adjustSizedBox : function(box, index) {
35706         var bodyBox = this.info.bodyBox,
35707             frameSize = this.frameSize,
35708             info = this.info,
35709             padding = info.padding,
35710             pos = box.type,
35711             border = info.border;
35712
35713         switch (pos) {
35714             case 'top':
35715                 box.y = bodyBox.y;
35716                 break;
35717
35718             case 'left':
35719                 box.x = bodyBox.x;
35720                 break;
35721
35722             case 'bottom':
35723                 box.y = (bodyBox.y + bodyBox.height) - box.height;
35724                 break;
35725
35726             case 'right':
35727                 box.x = (bodyBox.x + bodyBox.width) - box.width;
35728                 break;
35729         }
35730
35731         if (box.ignoreFrame) {
35732             if (pos == 'bottom') {
35733                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35734             }
35735             else {
35736                 box.y -= (frameSize.top + padding.top + border.top);
35737             }
35738             if (pos == 'right') {
35739                 box.x += (frameSize.right + padding.right + border.right);
35740             }
35741             else {
35742                 box.x -= (frameSize.left + padding.left + border.left);
35743             }
35744         }
35745
35746         // If this is not an overlaying docked item, we have to adjust the body box
35747         if (!box.overlay) {
35748             switch (pos) {
35749                 case 'top':
35750                     bodyBox.y += box.height;
35751                     bodyBox.height -= box.height;
35752                     break;
35753
35754                 case 'left':
35755                     bodyBox.x += box.width;
35756                     bodyBox.width -= box.width;
35757                     break;
35758
35759                 case 'bottom':
35760                     bodyBox.height -= box.height;
35761                     break;
35762
35763                 case 'right':
35764                     bodyBox.width -= box.width;
35765                     break;
35766             }
35767         }
35768         return box;
35769     },
35770
35771     /**
35772      * @protected
35773      * This method will adjust the position of the docked item inside an AutoContainerLayout
35774      * and adjust the body box accordingly.
35775      * @param {Object} box The box containing information about the width and height
35776      * of this docked item
35777      * @param {Number} index The index position of this docked item
35778      * @return {Object} The adjusted box
35779      */
35780     adjustAutoBox : function (box, index) {
35781         var info = this.info,
35782             owner = this.owner,
35783             bodyBox = info.bodyBox,
35784             size = info.size,
35785             boxes = info.boxes,
35786             boxesLn = boxes.length,
35787             pos = box.type,
35788             frameSize = this.frameSize,
35789             padding = info.padding,
35790             border = info.border,
35791             autoSizedCtLayout = info.autoSizedCtLayout,
35792             ln = (boxesLn < index) ? boxesLn : index,
35793             i, adjustBox;
35794
35795         if (pos == 'top' || pos == 'bottom') {
35796             // This can affect the previously set left and right and bottom docked items
35797             for (i = 0; i < ln; i++) {
35798                 adjustBox = boxes[i];
35799                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
35800                     adjustBox.height += box.height;
35801                 }
35802                 else if (adjustBox.type == 'bottom') {
35803                     adjustBox.y += box.height;
35804                 }
35805             }
35806         }
35807
35808         switch (pos) {
35809             case 'top':
35810                 box.y = bodyBox.y;
35811                 if (!box.overlay) {
35812                     bodyBox.y += box.height;
35813                     if (info.autoHeight) {
35814                         size.height += box.height;
35815                     } else {
35816                         bodyBox.height -= box.height;
35817                     }
35818                 }
35819                 break;
35820
35821             case 'bottom':
35822                 if (!box.overlay) {
35823                     if (info.autoHeight) {
35824                         size.height += box.height;
35825                     } else {
35826                         bodyBox.height -= box.height;
35827                     }
35828                 }
35829                 box.y = (bodyBox.y + bodyBox.height);
35830                 break;
35831
35832             case 'left':
35833                 box.x = bodyBox.x;
35834                 if (!box.overlay) {
35835                     bodyBox.x += box.width;
35836                     if (info.autoWidth) {
35837                         size.width += box.width;
35838                     } else {
35839                         bodyBox.width -= box.width;
35840                     }
35841                 }
35842                 break;
35843
35844             case 'right':
35845                 if (!box.overlay) {
35846                     if (info.autoWidth) {
35847                         size.width += box.width;
35848                     } else {
35849                         bodyBox.width -= box.width;
35850                     }
35851                 }
35852                 box.x = (bodyBox.x + bodyBox.width);
35853                 break;
35854         }
35855
35856         if (box.ignoreFrame) {
35857             if (pos == 'bottom') {
35858                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35859             }
35860             else {
35861                 box.y -= (frameSize.top + padding.top + border.top);
35862             }
35863             if (pos == 'right') {
35864                 box.x += (frameSize.right + padding.right + border.right);
35865             }
35866             else {
35867                 box.x -= (frameSize.left + padding.left + border.left);
35868             }
35869         }
35870         return box;
35871     },
35872
35873     /**
35874      * @protected
35875      * This method will create a box object, with a reference to the item, the type of dock
35876      * (top, left, bottom, right). It will also take care of stretching and aligning of the
35877      * docked items.
35878      * @param {Ext.Component} item The docked item we want to initialize the box for
35879      * @return {Object} The initial box containing width and height and other useful information
35880      */
35881     initBox : function(item) {
35882         var me = this,
35883             bodyBox = me.info.bodyBox,
35884             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
35885             owner = me.owner,
35886             frameSize = me.frameSize,
35887             info = me.info,
35888             padding = info.padding,
35889             border = info.border,
35890             box = {
35891                 item: item,
35892                 overlay: item.overlay,
35893                 type: item.dock,
35894                 offsets: Ext.Element.parseBox(item.offsets || {}),
35895                 ignoreFrame: item.ignoreParentFrame
35896             };
35897         // First we are going to take care of stretch and align properties for all four dock scenarios.
35898         if (item.stretch !== false) {
35899             box.stretched = true;
35900             if (horizontal) {
35901                 box.x = bodyBox.x + box.offsets.left;
35902                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
35903                 if (box.ignoreFrame) {
35904                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
35905                 }
35906                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
35907             }
35908             else {
35909                 box.y = bodyBox.y + box.offsets.top;
35910                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
35911                 if (box.ignoreFrame) {
35912                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
35913                 }
35914                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
35915
35916                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
35917                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
35918                 if (!Ext.supports.ComputedStyle) {
35919                     item.el.repaint();
35920                 }
35921             }
35922         }
35923         else {
35924             item.doComponentLayout();
35925             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
35926             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
35927             box.y += box.offsets.top;
35928             if (horizontal) {
35929                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
35930                 box.x += box.offsets.left;
35931             }
35932         }
35933
35934         // If we haven't calculated the width or height of the docked item yet
35935         // do so, since we need this for our upcoming calculations
35936         if (box.width === undefined) {
35937             box.width = item.getWidth() + item.el.getMargin('lr');
35938         }
35939         if (box.height === undefined) {
35940             box.height = item.getHeight() + item.el.getMargin('tb');
35941         }
35942
35943         return box;
35944     },
35945
35946     /**
35947      * @protected
35948      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
35949      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
35950      */
35951     getLayoutItems : function() {
35952         var it = this.owner.getDockedItems(),
35953             ln = it.length,
35954             i = 0,
35955             result = [];
35956         for (; i < ln; i++) {
35957             if (it[i].isVisible(true)) {
35958                 result.push(it[i]);
35959             }
35960         }
35961         return result;
35962     },
35963
35964     /**
35965      * @protected
35966      * Render the top and left docked items before any existing DOM nodes in our render target,
35967      * and then render the right and bottom docked items after. This is important, for such things
35968      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
35969      * Our collection of docked items will already be ordered via Panel.getDockedItems().
35970      */
35971     renderItems: function(items, target) {
35972         var cns = target.dom.childNodes,
35973             cnsLn = cns.length,
35974             ln = items.length,
35975             domLn = 0,
35976             i, j, cn, item;
35977
35978         // Calculate the number of DOM nodes in our target that are not our docked items
35979         for (i = 0; i < cnsLn; i++) {
35980             cn = Ext.get(cns[i]);
35981             for (j = 0; j < ln; j++) {
35982                 item = items[j];
35983                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
35984                     break;
35985                 }
35986             }
35987
35988             if (j === ln) {
35989                 domLn++;
35990             }
35991         }
35992
35993         // Now we go through our docked items and render/move them
35994         for (i = 0, j = 0; i < ln; i++, j++) {
35995             item = items[i];
35996
35997             // If we're now at the right/bottom docked item, we jump ahead in our
35998             // DOM position, just past the existing DOM nodes.
35999             //
36000             // TODO: This is affected if users provide custom weight values to their
36001             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
36002             // sort operation here, for now, in the name of performance. getDockedItems()
36003             // needs the sort operation not just for this layout-time rendering, but
36004             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
36005             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
36006                 j += domLn;
36007             }
36008
36009             // Same logic as Layout.renderItems()
36010             if (item && !item.rendered) {
36011                 this.renderItem(item, target, j);
36012             }
36013             else if (!this.isValidParent(item, target, j)) {
36014                 this.moveItem(item, target, j);
36015             }
36016         }
36017     },
36018
36019     /**
36020      * @protected
36021      * This function will be called by the dockItems method. Since the body is positioned absolute,
36022      * we need to give it dimensions and a position so that it is in the middle surrounded by
36023      * docked items
36024      * @param {Object} box An object containing new x, y, width and height values for the
36025      * Panel's body
36026      */
36027     setBodyBox : function(box) {
36028         var me = this,
36029             owner = me.owner,
36030             body = owner.body,
36031             info = me.info,
36032             bodyMargin = info.bodyMargin,
36033             padding = info.padding,
36034             border = info.border,
36035             frameSize = me.frameSize;
36036         
36037         // Panel collapse effectively hides the Panel's body, so this is a no-op.
36038         if (owner.collapsed) {
36039             return;
36040         }
36041         
36042         if (Ext.isNumber(box.width)) {
36043             box.width -= bodyMargin.left + bodyMargin.right;
36044         }
36045         
36046         if (Ext.isNumber(box.height)) {
36047             box.height -= bodyMargin.top + bodyMargin.bottom;
36048         }
36049         
36050         me.setElementSize(body, box.width, box.height);
36051         if (Ext.isNumber(box.x)) {
36052             body.setLeft(box.x - padding.left - frameSize.left);
36053         }
36054         if (Ext.isNumber(box.y)) {
36055             body.setTop(box.y - padding.top - frameSize.top);
36056         }
36057     },
36058
36059     /**
36060      * @protected
36061      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
36062      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
36063      * An example of a class added to a dock: right item is x-docked-right
36064      * @param {Ext.Component} item The item we are configuring
36065      */
36066     configureItem : function(item, pos) {
36067         this.callParent(arguments);
36068         if (item.dock == 'top' || item.dock == 'bottom') {
36069             item.layoutManagedWidth = 1;
36070             item.layoutManagedHeight = 2;
36071         } else {
36072             item.layoutManagedWidth = 2;
36073             item.layoutManagedHeight = 1;
36074         }
36075         
36076         item.addCls(Ext.baseCSSPrefix + 'docked');
36077         item.addClsWithUI('docked-' + item.dock);
36078     },
36079
36080     afterRemove : function(item) {
36081         this.callParent(arguments);
36082         if (this.itemCls) {
36083             item.el.removeCls(this.itemCls + '-' + item.dock);
36084         }
36085         var dom = item.el.dom;
36086
36087         if (!item.destroying && dom) {
36088             dom.parentNode.removeChild(dom);
36089         }
36090         this.childrenChanged = true;
36091     }
36092 });
36093 /**
36094  * @class Ext.util.Memento
36095  * This class manages a set of captured properties from an object. These captured properties
36096  * can later be restored to an object.
36097  */
36098 Ext.define('Ext.util.Memento', function () {
36099
36100     function captureOne (src, target, prop) {
36101         src[prop] = target[prop];
36102     }
36103
36104     function removeOne (src, target, prop) {
36105         delete src[prop];
36106     }
36107
36108     function restoreOne (src, target, prop) {
36109         var value = src[prop];
36110         if (value || src.hasOwnProperty(prop)) {
36111             restoreValue(target, prop, value);
36112         }
36113     }
36114
36115     function restoreValue (target, prop, value) {
36116         if (Ext.isDefined(value)) {
36117             target[prop] = value;
36118         } else {
36119             delete target[prop];
36120         }
36121     }
36122
36123     function doMany (doOne, src, target, props) {
36124         if (src) {
36125             if (Ext.isArray(props)) {
36126                 Ext.each(props, function (prop) {
36127                     doOne(src, target, prop);
36128                 });
36129             } else {
36130                 doOne(src, target, props);
36131             }
36132         }
36133     }
36134
36135     return {
36136         /**
36137          * @property data
36138          * The collection of captured properties.
36139          * @private
36140          */
36141         data: null,
36142
36143         /**
36144          * @property target
36145          * The default target object for capture/restore (passed to the constructor).
36146          */
36147         target: null,
36148
36149         /**
36150          * Creates a new memento and optionally captures properties from the target object.
36151          * @param {Object} target The target from which to capture properties. If specified in the
36152          * constructor, this target becomes the default target for all other operations.
36153          * @param {String/String[]} props The property or array of properties to capture.
36154          */
36155         constructor: function (target, props) {
36156             if (target) {
36157                 this.target = target;
36158                 if (props) {
36159                     this.capture(props);
36160                 }
36161             }
36162         },
36163
36164         /**
36165          * Captures the specified properties from the target object in this memento.
36166          * @param {String/String[]} props The property or array of properties to capture.
36167          * @param {Object} target The object from which to capture properties.
36168          */
36169         capture: function (props, target) {
36170             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
36171         },
36172
36173         /**
36174          * Removes the specified properties from this memento. These properties will not be
36175          * restored later without re-capturing their values.
36176          * @param {String/String[]} props The property or array of properties to remove.
36177          */
36178         remove: function (props) {
36179             doMany(removeOne, this.data, null, props);
36180         },
36181
36182         /**
36183          * Restores the specified properties from this memento to the target object.
36184          * @param {String/String[]} props The property or array of properties to restore.
36185          * @param {Boolean} clear True to remove the restored properties from this memento or
36186          * false to keep them (default is true).
36187          * @param {Object} target The object to which to restore properties.
36188          */
36189         restore: function (props, clear, target) {
36190             doMany(restoreOne, this.data, target || this.target, props);
36191             if (clear !== false) {
36192                 this.remove(props);
36193             }
36194         },
36195
36196         /**
36197          * Restores all captured properties in this memento to the target object.
36198          * @param {Boolean} clear True to remove the restored properties from this memento or
36199          * false to keep them (default is true).
36200          * @param {Object} target The object to which to restore properties.
36201          */
36202         restoreAll: function (clear, target) {
36203             var me = this,
36204                 t = target || this.target;
36205
36206             Ext.Object.each(me.data, function (prop, value) {
36207                 restoreValue(t, prop, value);
36208             });
36209
36210             if (clear !== false) {
36211                 delete me.data;
36212             }
36213         }
36214     };
36215 }());
36216
36217 /**
36218  * @class Ext.app.EventBus
36219  * @private
36220  */
36221 Ext.define('Ext.app.EventBus', {
36222     requires: [
36223         'Ext.util.Event'
36224     ],
36225     mixins: {
36226         observable: 'Ext.util.Observable'
36227     },
36228
36229     constructor: function() {
36230         this.mixins.observable.constructor.call(this);
36231
36232         this.bus = {};
36233
36234         var me = this;
36235         Ext.override(Ext.Component, {
36236             fireEvent: function(ev) {
36237                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
36238                     return me.dispatch.call(me, ev, this, arguments);
36239                 }
36240                 return false;
36241             }
36242         });
36243     },
36244
36245     dispatch: function(ev, target, args) {
36246         var bus = this.bus,
36247             selectors = bus[ev],
36248             selector, controllers, id, events, event, i, ln;
36249
36250         if (selectors) {
36251             // Loop over all the selectors that are bound to this event
36252             for (selector in selectors) {
36253                 // Check if the target matches the selector
36254                 if (target.is(selector)) {
36255                     // Loop over all the controllers that are bound to this selector
36256                     controllers = selectors[selector];
36257                     for (id in controllers) {
36258                         // Loop over all the events that are bound to this selector on this controller
36259                         events = controllers[id];
36260                         for (i = 0, ln = events.length; i < ln; i++) {
36261                             event = events[i];
36262                             // Fire the event!
36263                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
36264                                 return false;
36265                             };
36266                         }
36267                     }
36268                 }
36269             }
36270         }
36271     },
36272
36273     control: function(selectors, listeners, controller) {
36274         var bus = this.bus,
36275             selector, fn;
36276
36277         if (Ext.isString(selectors)) {
36278             selector = selectors;
36279             selectors = {};
36280             selectors[selector] = listeners;
36281             this.control(selectors, null, controller);
36282             return;
36283         }
36284
36285         Ext.Object.each(selectors, function(selector, listeners) {
36286             Ext.Object.each(listeners, function(ev, listener) {
36287                 var options = {},
36288                     scope = controller,
36289                     event = Ext.create('Ext.util.Event', controller, ev);
36290
36291                 // Normalize the listener
36292                 if (Ext.isObject(listener)) {
36293                     options = listener;
36294                     listener = options.fn;
36295                     scope = options.scope || controller;
36296                     delete options.fn;
36297                     delete options.scope;
36298                 }
36299
36300                 event.addListener(listener, scope, options);
36301
36302                 // Create the bus tree if it is not there yet
36303                 bus[ev] = bus[ev] || {};
36304                 bus[ev][selector] = bus[ev][selector] || {};
36305                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
36306
36307                 // Push our listener in our bus
36308                 bus[ev][selector][controller.id].push(event);
36309             });
36310         });
36311     }
36312 });
36313 /**
36314  * @class Ext.data.Types
36315  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
36316  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
36317  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
36318  * of this class.</p>
36319  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
36320  * each type definition must contain three properties:</p>
36321  * <div class="mdetail-params"><ul>
36322  * <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
36323  * to be stored in the Field. The function is passed the collowing parameters:
36324  * <div class="mdetail-params"><ul>
36325  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36326  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
36327  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
36328  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
36329  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
36330  * </ul></div></div></li>
36331  * <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>
36332  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
36333  * </ul></div>
36334  * <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
36335  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
36336  *<pre><code>
36337 // Add a new Field data type which stores a VELatLong object in the Record.
36338 Ext.data.Types.VELATLONG = {
36339     convert: function(v, data) {
36340         return new VELatLong(data.lat, data.long);
36341     },
36342     sortType: function(v) {
36343         return v.Latitude;  // When sorting, order by latitude
36344     },
36345     type: 'VELatLong'
36346 };
36347 </code></pre>
36348  * <p>Then, when declaring a Model, use: <pre><code>
36349 var types = Ext.data.Types; // allow shorthand type access
36350 Ext.define('Unit',
36351     extend: 'Ext.data.Model',
36352     fields: [
36353         { name: 'unitName', mapping: 'UnitName' },
36354         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
36355         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
36356         { name: 'longitude', mapping: 'long', type: types.FLOAT },
36357         { name: 'position', type: types.VELATLONG }
36358     ]
36359 });
36360 </code></pre>
36361  * @singleton
36362  */
36363 Ext.define('Ext.data.Types', {
36364     singleton: true,
36365     requires: ['Ext.data.SortTypes']
36366 }, function() {
36367     var st = Ext.data.SortTypes;
36368
36369     Ext.apply(Ext.data.Types, {
36370         /**
36371          * @property {RegExp} stripRe
36372          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
36373          * This should be overridden for localization.
36374          */
36375         stripRe: /[\$,%]/g,
36376
36377         /**
36378          * @property {Object} AUTO
36379          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
36380          */
36381         AUTO: {
36382             convert: function(v) {
36383                 return v;
36384             },
36385             sortType: st.none,
36386             type: 'auto'
36387         },
36388
36389         /**
36390          * @property {Object} STRING
36391          * This data type means that the raw data is converted into a String before it is placed into a Record.
36392          */
36393         STRING: {
36394             convert: function(v) {
36395                 var defaultValue = this.useNull ? null : '';
36396                 return (v === undefined || v === null) ? defaultValue : String(v);
36397             },
36398             sortType: st.asUCString,
36399             type: 'string'
36400         },
36401
36402         /**
36403          * @property {Object} INT
36404          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36405          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
36406          */
36407         INT: {
36408             convert: function(v) {
36409                 return v !== undefined && v !== null && v !== '' ?
36410                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36411             },
36412             sortType: st.none,
36413             type: 'int'
36414         },
36415
36416         /**
36417          * @property {Object} FLOAT
36418          * This data type means that the raw data is converted into a number before it is placed into a Record.
36419          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
36420          */
36421         FLOAT: {
36422             convert: function(v) {
36423                 return v !== undefined && v !== null && v !== '' ?
36424                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36425             },
36426             sortType: st.none,
36427             type: 'float'
36428         },
36429
36430         /**
36431          * @property {Object} BOOL
36432          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36433          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36434          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
36435          */
36436         BOOL: {
36437             convert: function(v) {
36438                 if (this.useNull && (v === undefined || v === null || v === '')) {
36439                     return null;
36440                 }
36441                 return v === true || v === 'true' || v == 1;
36442             },
36443             sortType: st.none,
36444             type: 'bool'
36445         },
36446
36447         /**
36448          * @property {Object} DATE
36449          * This data type means that the raw data is converted into a Date before it is placed into a Record.
36450          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
36451          * being applied.
36452          */
36453         DATE: {
36454             convert: function(v) {
36455                 var df = this.dateFormat,
36456                     parsed;
36457
36458                 if (!v) {
36459                     return null;
36460                 }
36461                 if (Ext.isDate(v)) {
36462                     return v;
36463                 }
36464                 if (df) {
36465                     if (df == 'timestamp') {
36466                         return new Date(v*1000);
36467                     }
36468                     if (df == 'time') {
36469                         return new Date(parseInt(v, 10));
36470                     }
36471                     return Ext.Date.parse(v, df);
36472                 }
36473
36474                 parsed = Date.parse(v);
36475                 return parsed ? new Date(parsed) : null;
36476             },
36477             sortType: st.asDate,
36478             type: 'date'
36479         }
36480     });
36481
36482     Ext.apply(Ext.data.Types, {
36483         /**
36484          * @property {Object} BOOLEAN
36485          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36486          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36487          * <p>The synonym <code>BOOL</code> is equivalent.</p>
36488          */
36489         BOOLEAN: this.BOOL,
36490
36491         /**
36492          * @property {Object} INTEGER
36493          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36494          * <p>The synonym <code>INT</code> is equivalent.</p>
36495          */
36496         INTEGER: this.INT,
36497
36498         /**
36499          * @property {Object} NUMBER
36500          * This data type means that the raw data is converted into a number before it is placed into a Record.
36501          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
36502          */
36503         NUMBER: this.FLOAT
36504     });
36505 });
36506
36507 /**
36508  * @author Ed Spencer
36509  *
36510  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
36511  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
36512  * Ext.data.Model Model}. For example, we might set up a model like this:
36513  *
36514  *     Ext.define('User', {
36515  *         extend: 'Ext.data.Model',
36516  *         fields: [
36517  *             'name', 'email',
36518  *             {name: 'age', type: 'int'},
36519  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36520  *         ]
36521  *     });
36522  *
36523  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
36524  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
36525  * up with the 'auto' type. It's as if we'd done this instead:
36526  *
36527  *     Ext.define('User', {
36528  *         extend: 'Ext.data.Model',
36529  *         fields: [
36530  *             {name: 'name', type: 'auto'},
36531  *             {name: 'email', type: 'auto'},
36532  *             {name: 'age', type: 'int'},
36533  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36534  *         ]
36535  *     });
36536  *
36537  * # Types and conversion
36538  *
36539  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
36540  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
36541  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
36542  *
36543  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
36544  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
36545  *
36546  *     Ext.define('User', {
36547  *         extend: 'Ext.data.Model',
36548  *         fields: [
36549  *             'name', 'email',
36550  *             {name: 'age', type: 'int'},
36551  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
36552  *
36553  *             {
36554  *                 name: 'firstName',
36555  *                 convert: function(value, record) {
36556  *                     var fullName  = record.get('name'),
36557  *                         splits    = fullName.split(" "),
36558  *                         firstName = splits[0];
36559  *
36560  *                     return firstName;
36561  *                 }
36562  *             }
36563  *         ]
36564  *     });
36565  *
36566  * Now when we create a new User, the firstName is populated automatically based on the name:
36567  *
36568  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
36569  *
36570  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
36571  *
36572  * In fact, if we log out all of the data inside ed, we'll see this:
36573  *
36574  *     console.log(ed.data);
36575  *
36576  *     //outputs this:
36577  *     {
36578  *         age: 0,
36579  *         email: "",
36580  *         firstName: "Ed",
36581  *         gender: "Unknown",
36582  *         name: "Ed Spencer"
36583  *     }
36584  *
36585  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
36586  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
36587  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
36588  *
36589  *     ed.set('gender', 'Male');
36590  *     ed.get('gender'); //returns 'Male'
36591  *
36592  *     ed.set('age', 25.4);
36593  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
36594  */
36595 Ext.define('Ext.data.Field', {
36596     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
36597     alias: 'data.field',
36598     
36599     constructor : function(config) {
36600         if (Ext.isString(config)) {
36601             config = {name: config};
36602         }
36603         Ext.apply(this, config);
36604         
36605         var types = Ext.data.Types,
36606             st = this.sortType,
36607             t;
36608
36609         if (this.type) {
36610             if (Ext.isString(this.type)) {
36611                 this.type = types[this.type.toUpperCase()] || types.AUTO;
36612             }
36613         } else {
36614             this.type = types.AUTO;
36615         }
36616
36617         // named sortTypes are supported, here we look them up
36618         if (Ext.isString(st)) {
36619             this.sortType = Ext.data.SortTypes[st];
36620         } else if(Ext.isEmpty(st)) {
36621             this.sortType = this.type.sortType;
36622         }
36623
36624         if (!this.convert) {
36625             this.convert = this.type.convert;
36626         }
36627     },
36628     
36629     /**
36630      * @cfg {String} name
36631      *
36632      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
36633      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
36634      *
36635      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
36636      * just a String for the field name.
36637      */
36638     
36639     /**
36640      * @cfg {String/Object} type
36641      *
36642      * The data type for automatic conversion from received data to the *stored* value if
36643      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
36644      * Possible values are
36645      *
36646      * - auto (Default, implies no conversion)
36647      * - string
36648      * - int
36649      * - float
36650      * - boolean
36651      * - date
36652      *
36653      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
36654      *
36655      * Developers may create their own application-specific data types by defining new members of the {@link
36656      * Ext.data.Types} class.
36657      */
36658     
36659     /**
36660      * @cfg {Function} convert
36661      *
36662      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
36663      * It is passed the following parameters:
36664      *
36665      * - **v** : Mixed
36666      *
36667      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
36668      *   defaultValue}`.
36669      *
36670      * - **rec** : Ext.data.Model
36671      *
36672      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
36673      *   at this point as the fields are read in the order that they are defined in your
36674      *   {@link Ext.data.Model#fields fields} array.
36675      *
36676      * Example of convert functions:
36677      *
36678      *     function fullName(v, record){
36679      *         return record.name.last + ', ' + record.name.first;
36680      *     }
36681      *
36682      *     function location(v, record){
36683      *         return !record.city ? '' : (record.city + ', ' + record.state);
36684      *     }
36685      *
36686      *     Ext.define('Dude', {
36687      *         extend: 'Ext.data.Model',
36688      *         fields: [
36689      *             {name: 'fullname',  convert: fullName},
36690      *             {name: 'firstname', mapping: 'name.first'},
36691      *             {name: 'lastname',  mapping: 'name.last'},
36692      *             {name: 'city', defaultValue: 'homeless'},
36693      *             'state',
36694      *             {name: 'location',  convert: location}
36695      *         ]
36696      *     });
36697      *
36698      *     // create the data store
36699      *     var store = Ext.create('Ext.data.Store', {
36700      *         reader: {
36701      *             type: 'json',
36702      *             model: 'Dude',
36703      *             idProperty: 'key',
36704      *             root: 'daRoot',
36705      *             totalProperty: 'total'
36706      *         }
36707      *     });
36708      *
36709      *     var myData = [
36710      *         { key: 1,
36711      *           name: { first: 'Fat',    last:  'Albert' }
36712      *           // notice no city, state provided in data object
36713      *         },
36714      *         { key: 2,
36715      *           name: { first: 'Barney', last:  'Rubble' },
36716      *           city: 'Bedrock', state: 'Stoneridge'
36717      *         },
36718      *         { key: 3,
36719      *           name: { first: 'Cliff',  last:  'Claven' },
36720      *           city: 'Boston',  state: 'MA'
36721      *         }
36722      *     ];
36723      */
36724
36725     /**
36726      * @cfg {String} dateFormat
36727      *
36728      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
36729      *
36730      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
36731      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
36732      * timestamp. See {@link Ext.Date}.
36733      */
36734     dateFormat: null,
36735     
36736     /**
36737      * @cfg {Boolean} useNull
36738      *
36739      * Use when converting received data into a Number type (either int or float). If the value cannot be
36740      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
36741      */
36742     useNull: false,
36743     
36744     /**
36745      * @cfg {Object} defaultValue
36746      *
36747      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
36748      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
36749      * (i.e. undefined). Defaults to "".
36750      */
36751     defaultValue: "",
36752
36753     /**
36754      * @cfg {String/Number} mapping
36755      *
36756      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
36757      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
36758      * as the field name, the mapping may be omitted.
36759      *
36760      * The form of the mapping expression depends on the Reader being used.
36761      *
36762      * - {@link Ext.data.reader.Json}
36763      *
36764      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
36765      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
36766      *
36767      * - {@link Ext.data.reader.Xml}
36768      *
36769      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
36770      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
36771      *
36772      * - {@link Ext.data.reader.Array}
36773      *
36774      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
36775      *   Array position.
36776      *
36777      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
36778      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
36779      * return the desired data.
36780      */
36781     mapping: null,
36782
36783     /**
36784      * @cfg {Function} sortType
36785      *
36786      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
36787      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
36788      *
36789      *     // current sort     after sort we want
36790      *     // +-+------+          +-+------+
36791      *     // |1|First |          |1|First |
36792      *     // |2|Last  |          |3|Second|
36793      *     // |3|Second|          |2|Last  |
36794      *     // +-+------+          +-+------+
36795      *
36796      *     sortType: function(value) {
36797      *        switch (value.toLowerCase()) // native toLowerCase():
36798      *        {
36799      *           case 'first': return 1;
36800      *           case 'second': return 2;
36801      *           default: return 3;
36802      *        }
36803      *     }
36804      */
36805     sortType : null,
36806
36807     /**
36808      * @cfg {String} sortDir
36809      *
36810      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
36811      */
36812     sortDir : "ASC",
36813
36814     /**
36815      * @cfg {Boolean} allowBlank
36816      * @private
36817      *
36818      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
36819      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
36820      */
36821     allowBlank : true,
36822
36823     /**
36824      * @cfg {Boolean} persist
36825      *
36826      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
36827      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
36828      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
36829      */
36830     persist: true
36831 });
36832
36833 /**
36834  * @class Ext.util.AbstractMixedCollection
36835  * @private
36836  */
36837 Ext.define('Ext.util.AbstractMixedCollection', {
36838     requires: ['Ext.util.Filter'],
36839
36840     mixins: {
36841         observable: 'Ext.util.Observable'
36842     },
36843
36844     constructor: function(allowFunctions, keyFn) {
36845         var me = this;
36846
36847         me.items = [];
36848         me.map = {};
36849         me.keys = [];
36850         me.length = 0;
36851
36852         me.addEvents(
36853             /**
36854              * @event clear
36855              * Fires when the collection is cleared.
36856              */
36857             'clear',
36858
36859             /**
36860              * @event add
36861              * Fires when an item is added to the collection.
36862              * @param {Number} index The index at which the item was added.
36863              * @param {Object} o The item added.
36864              * @param {String} key The key associated with the added item.
36865              */
36866             'add',
36867
36868             /**
36869              * @event replace
36870              * Fires when an item is replaced in the collection.
36871              * @param {String} key he key associated with the new added.
36872              * @param {Object} old The item being replaced.
36873              * @param {Object} new The new item.
36874              */
36875             'replace',
36876
36877             /**
36878              * @event remove
36879              * Fires when an item is removed from the collection.
36880              * @param {Object} o The item being removed.
36881              * @param {String} key (optional) The key associated with the removed item.
36882              */
36883             'remove'
36884         );
36885
36886         me.allowFunctions = allowFunctions === true;
36887
36888         if (keyFn) {
36889             me.getKey = keyFn;
36890         }
36891
36892         me.mixins.observable.constructor.call(me);
36893     },
36894
36895     /**
36896      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
36897      * function should add function references to the collection. Defaults to
36898      * <tt>false</tt>.
36899      */
36900     allowFunctions : false,
36901
36902     /**
36903      * Adds an item to the collection. Fires the {@link #add} event when complete.
36904      * @param {String} key <p>The key to associate with the item, or the new item.</p>
36905      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
36906      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
36907      * the MixedCollection will be able to <i>derive</i> the key for the new item.
36908      * In this case just pass the new item in this parameter.</p>
36909      * @param {Object} o The item to add.
36910      * @return {Object} The item added.
36911      */
36912     add : function(key, obj){
36913         var me = this,
36914             myObj = obj,
36915             myKey = key,
36916             old;
36917
36918         if (arguments.length == 1) {
36919             myObj = myKey;
36920             myKey = me.getKey(myObj);
36921         }
36922         if (typeof myKey != 'undefined' && myKey !== null) {
36923             old = me.map[myKey];
36924             if (typeof old != 'undefined') {
36925                 return me.replace(myKey, myObj);
36926             }
36927             me.map[myKey] = myObj;
36928         }
36929         me.length++;
36930         me.items.push(myObj);
36931         me.keys.push(myKey);
36932         me.fireEvent('add', me.length - 1, myObj, myKey);
36933         return myObj;
36934     },
36935
36936     /**
36937       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
36938       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
36939       * to return a different value as in the following examples:<pre><code>
36940 // normal way
36941 var mc = new Ext.util.MixedCollection();
36942 mc.add(someEl.dom.id, someEl);
36943 mc.add(otherEl.dom.id, otherEl);
36944 //and so on
36945
36946 // using getKey
36947 var mc = new Ext.util.MixedCollection();
36948 mc.getKey = function(el){
36949    return el.dom.id;
36950 };
36951 mc.add(someEl);
36952 mc.add(otherEl);
36953
36954 // or via the constructor
36955 var mc = new Ext.util.MixedCollection(false, function(el){
36956    return el.dom.id;
36957 });
36958 mc.add(someEl);
36959 mc.add(otherEl);
36960      * </code></pre>
36961      * @param {Object} item The item for which to find the key.
36962      * @return {Object} The key for the passed item.
36963      */
36964     getKey : function(o){
36965          return o.id;
36966     },
36967
36968     /**
36969      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
36970      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
36971      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
36972      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
36973      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
36974      * with one having the same key value, then just pass the replacement item in this parameter.</p>
36975      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
36976      * with that key.
36977      * @return {Object}  The new item.
36978      */
36979     replace : function(key, o){
36980         var me = this,
36981             old,
36982             index;
36983
36984         if (arguments.length == 1) {
36985             o = arguments[0];
36986             key = me.getKey(o);
36987         }
36988         old = me.map[key];
36989         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
36990              return me.add(key, o);
36991         }
36992         index = me.indexOfKey(key);
36993         me.items[index] = o;
36994         me.map[key] = o;
36995         me.fireEvent('replace', key, old, o);
36996         return o;
36997     },
36998
36999     /**
37000      * Adds all elements of an Array or an Object to the collection.
37001      * @param {Object/Array} objs An Object containing properties which will be added
37002      * to the collection, or an Array of values, each of which are added to the collection.
37003      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
37004      * has been set to <tt>true</tt>.
37005      */
37006     addAll : function(objs){
37007         var me = this,
37008             i = 0,
37009             args,
37010             len,
37011             key;
37012
37013         if (arguments.length > 1 || Ext.isArray(objs)) {
37014             args = arguments.length > 1 ? arguments : objs;
37015             for (len = args.length; i < len; i++) {
37016                 me.add(args[i]);
37017             }
37018         } else {
37019             for (key in objs) {
37020                 if (objs.hasOwnProperty(key)) {
37021                     if (me.allowFunctions || typeof objs[key] != 'function') {
37022                         me.add(key, objs[key]);
37023                     }
37024                 }
37025             }
37026         }
37027     },
37028
37029     /**
37030      * Executes the specified function once for every item in the collection, passing the following arguments:
37031      * <div class="mdetail-params"><ul>
37032      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
37033      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
37034      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
37035      * </ul></div>
37036      * The function should return a boolean value. Returning false from the function will stop the iteration.
37037      * @param {Function} fn The function to execute for each item.
37038      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
37039      */
37040     each : function(fn, scope){
37041         var items = [].concat(this.items), // each safe for removal
37042             i = 0,
37043             len = items.length,
37044             item;
37045
37046         for (; i < len; i++) {
37047             item = items[i];
37048             if (fn.call(scope || item, item, i, len) === false) {
37049                 break;
37050             }
37051         }
37052     },
37053
37054     /**
37055      * Executes the specified function once for every key in the collection, passing each
37056      * key, and its associated item as the first two parameters.
37057      * @param {Function} fn The function to execute for each item.
37058      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37059      */
37060     eachKey : function(fn, scope){
37061         var keys = this.keys,
37062             items = this.items,
37063             i = 0,
37064             len = keys.length;
37065
37066         for (; i < len; i++) {
37067             fn.call(scope || window, keys[i], items[i], i, len);
37068         }
37069     },
37070
37071     /**
37072      * Returns the first item in the collection which elicits a true return value from the
37073      * passed selection function.
37074      * @param {Function} fn The selection function to execute for each item.
37075      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37076      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
37077      */
37078     findBy : function(fn, scope) {
37079         var keys = this.keys,
37080             items = this.items,
37081             i = 0,
37082             len = items.length;
37083
37084         for (; i < len; i++) {
37085             if (fn.call(scope || window, items[i], keys[i])) {
37086                 return items[i];
37087             }
37088         }
37089         return null;
37090     },
37091
37092     find : function() {
37093         if (Ext.isDefined(Ext.global.console)) {
37094             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
37095         }
37096         return this.findBy.apply(this, arguments);
37097     },
37098
37099     /**
37100      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
37101      * @param {Number} index The index to insert the item at.
37102      * @param {String} key The key to associate with the new item, or the item itself.
37103      * @param {Object} o (optional) If the second parameter was a key, the new item.
37104      * @return {Object} The item inserted.
37105      */
37106     insert : function(index, key, obj){
37107         var me = this,
37108             myKey = key,
37109             myObj = obj;
37110
37111         if (arguments.length == 2) {
37112             myObj = myKey;
37113             myKey = me.getKey(myObj);
37114         }
37115         if (me.containsKey(myKey)) {
37116             me.suspendEvents();
37117             me.removeAtKey(myKey);
37118             me.resumeEvents();
37119         }
37120         if (index >= me.length) {
37121             return me.add(myKey, myObj);
37122         }
37123         me.length++;
37124         Ext.Array.splice(me.items, index, 0, myObj);
37125         if (typeof myKey != 'undefined' && myKey !== null) {
37126             me.map[myKey] = myObj;
37127         }
37128         Ext.Array.splice(me.keys, index, 0, myKey);
37129         me.fireEvent('add', index, myObj, myKey);
37130         return myObj;
37131     },
37132
37133     /**
37134      * Remove an item from the collection.
37135      * @param {Object} o The item to remove.
37136      * @return {Object} The item removed or false if no item was removed.
37137      */
37138     remove : function(o){
37139         return this.removeAt(this.indexOf(o));
37140     },
37141
37142     /**
37143      * Remove all items in the passed array from the collection.
37144      * @param {Array} items An array of items to be removed.
37145      * @return {Ext.util.MixedCollection} this object
37146      */
37147     removeAll : function(items){
37148         Ext.each(items || [], function(item) {
37149             this.remove(item);
37150         }, this);
37151
37152         return this;
37153     },
37154
37155     /**
37156      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
37157      * @param {Number} index The index within the collection of the item to remove.
37158      * @return {Object} The item removed or false if no item was removed.
37159      */
37160     removeAt : function(index){
37161         var me = this,
37162             o,
37163             key;
37164
37165         if (index < me.length && index >= 0) {
37166             me.length--;
37167             o = me.items[index];
37168             Ext.Array.erase(me.items, index, 1);
37169             key = me.keys[index];
37170             if (typeof key != 'undefined') {
37171                 delete me.map[key];
37172             }
37173             Ext.Array.erase(me.keys, index, 1);
37174             me.fireEvent('remove', o, key);
37175             return o;
37176         }
37177         return false;
37178     },
37179
37180     /**
37181      * Removed an item associated with the passed key fom the collection.
37182      * @param {String} key The key of the item to remove.
37183      * @return {Object} The item removed or false if no item was removed.
37184      */
37185     removeAtKey : function(key){
37186         return this.removeAt(this.indexOfKey(key));
37187     },
37188
37189     /**
37190      * Returns the number of items in the collection.
37191      * @return {Number} the number of items in the collection.
37192      */
37193     getCount : function(){
37194         return this.length;
37195     },
37196
37197     /**
37198      * Returns index within the collection of the passed Object.
37199      * @param {Object} o The item to find the index of.
37200      * @return {Number} index of the item. Returns -1 if not found.
37201      */
37202     indexOf : function(o){
37203         return Ext.Array.indexOf(this.items, o);
37204     },
37205
37206     /**
37207      * Returns index within the collection of the passed key.
37208      * @param {String} key The key to find the index of.
37209      * @return {Number} index of the key.
37210      */
37211     indexOfKey : function(key){
37212         return Ext.Array.indexOf(this.keys, key);
37213     },
37214
37215     /**
37216      * Returns the item associated with the passed key OR index.
37217      * Key has priority over index.  This is the equivalent
37218      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
37219      * @param {String/Number} key The key or index of the item.
37220      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
37221      * If an item was found, but is a Class, returns <tt>null</tt>.
37222      */
37223     get : function(key) {
37224         var me = this,
37225             mk = me.map[key],
37226             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
37227         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
37228     },
37229
37230     /**
37231      * Returns the item at the specified index.
37232      * @param {Number} index The index of the item.
37233      * @return {Object} The item at the specified index.
37234      */
37235     getAt : function(index) {
37236         return this.items[index];
37237     },
37238
37239     /**
37240      * Returns the item associated with the passed key.
37241      * @param {String/Number} key The key of the item.
37242      * @return {Object} The item associated with the passed key.
37243      */
37244     getByKey : function(key) {
37245         return this.map[key];
37246     },
37247
37248     /**
37249      * Returns true if the collection contains the passed Object as an item.
37250      * @param {Object} o  The Object to look for in the collection.
37251      * @return {Boolean} True if the collection contains the Object as an item.
37252      */
37253     contains : function(o){
37254         return Ext.Array.contains(this.items, o);
37255     },
37256
37257     /**
37258      * Returns true if the collection contains the passed Object as a key.
37259      * @param {String} key The key to look for in the collection.
37260      * @return {Boolean} True if the collection contains the Object as a key.
37261      */
37262     containsKey : function(key){
37263         return typeof this.map[key] != 'undefined';
37264     },
37265
37266     /**
37267      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
37268      */
37269     clear : function(){
37270         var me = this;
37271
37272         me.length = 0;
37273         me.items = [];
37274         me.keys = [];
37275         me.map = {};
37276         me.fireEvent('clear');
37277     },
37278
37279     /**
37280      * Returns the first item in the collection.
37281      * @return {Object} the first item in the collection..
37282      */
37283     first : function() {
37284         return this.items[0];
37285     },
37286
37287     /**
37288      * Returns the last item in the collection.
37289      * @return {Object} the last item in the collection..
37290      */
37291     last : function() {
37292         return this.items[this.length - 1];
37293     },
37294
37295     /**
37296      * Collects all of the values of the given property and returns their sum
37297      * @param {String} property The property to sum by
37298      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
37299      * summing fields in records, where the fields are all stored inside the 'data' object
37300      * @param {Number} [start=0] The record index to start at
37301      * @param {Number} [end=-1] The record index to end at
37302      * @return {Number} The total
37303      */
37304     sum: function(property, root, start, end) {
37305         var values = this.extractValues(property, root),
37306             length = values.length,
37307             sum    = 0,
37308             i;
37309
37310         start = start || 0;
37311         end   = (end || end === 0) ? end : length - 1;
37312
37313         for (i = start; i <= end; i++) {
37314             sum += values[i];
37315         }
37316
37317         return sum;
37318     },
37319
37320     /**
37321      * Collects unique values of a particular property in this MixedCollection
37322      * @param {String} property The property to collect on
37323      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37324      * summing fields in records, where the fields are all stored inside the 'data' object
37325      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
37326      * @return {Array} The unique values
37327      */
37328     collect: function(property, root, allowNull) {
37329         var values = this.extractValues(property, root),
37330             length = values.length,
37331             hits   = {},
37332             unique = [],
37333             value, strValue, i;
37334
37335         for (i = 0; i < length; i++) {
37336             value = values[i];
37337             strValue = String(value);
37338
37339             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
37340                 hits[strValue] = true;
37341                 unique.push(value);
37342             }
37343         }
37344
37345         return unique;
37346     },
37347
37348     /**
37349      * @private
37350      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
37351      * functions like sum and collect.
37352      * @param {String} property The property to extract
37353      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37354      * extracting field data from Model instances, where the fields are stored inside the 'data' object
37355      * @return {Array} The extracted values
37356      */
37357     extractValues: function(property, root) {
37358         var values = this.items;
37359
37360         if (root) {
37361             values = Ext.Array.pluck(values, root);
37362         }
37363
37364         return Ext.Array.pluck(values, property);
37365     },
37366
37367     /**
37368      * Returns a range of items in this collection
37369      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
37370      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
37371      * @return {Array} An array of items
37372      */
37373     getRange : function(start, end){
37374         var me = this,
37375             items = me.items,
37376             range = [],
37377             i;
37378
37379         if (items.length < 1) {
37380             return range;
37381         }
37382
37383         start = start || 0;
37384         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
37385         if (start <= end) {
37386             for (i = start; i <= end; i++) {
37387                 range[range.length] = items[i];
37388             }
37389         } else {
37390             for (i = start; i >= end; i--) {
37391                 range[range.length] = items[i];
37392             }
37393         }
37394         return range;
37395     },
37396
37397     /**
37398      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
37399      * property/value pair with optional parameters for substring matching and case sensitivity. See
37400      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
37401      * MixedCollection can be easily filtered by property like this:</p>
37402 <pre><code>
37403 //create a simple store with a few people defined
37404 var people = new Ext.util.MixedCollection();
37405 people.addAll([
37406     {id: 1, age: 25, name: 'Ed'},
37407     {id: 2, age: 24, name: 'Tommy'},
37408     {id: 3, age: 24, name: 'Arne'},
37409     {id: 4, age: 26, name: 'Aaron'}
37410 ]);
37411
37412 //a new MixedCollection containing only the items where age == 24
37413 var middleAged = people.filter('age', 24);
37414 </code></pre>
37415      *
37416      *
37417      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
37418      * @param {String/RegExp} value Either string that the property values
37419      * should start with or a RegExp to test against the property
37420      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
37421      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37422      * @return {Ext.util.MixedCollection} The new filtered collection
37423      */
37424     filter : function(property, value, anyMatch, caseSensitive) {
37425         var filters = [],
37426             filterFn;
37427
37428         //support for the simple case of filtering by property/value
37429         if (Ext.isString(property)) {
37430             filters.push(Ext.create('Ext.util.Filter', {
37431                 property     : property,
37432                 value        : value,
37433                 anyMatch     : anyMatch,
37434                 caseSensitive: caseSensitive
37435             }));
37436         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
37437             filters = filters.concat(property);
37438         }
37439
37440         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
37441         //so here we construct a function that combines these filters by ANDing them together
37442         filterFn = function(record) {
37443             var isMatch = true,
37444                 length = filters.length,
37445                 i;
37446
37447             for (i = 0; i < length; i++) {
37448                 var filter = filters[i],
37449                     fn     = filter.filterFn,
37450                     scope  = filter.scope;
37451
37452                 isMatch = isMatch && fn.call(scope, record);
37453             }
37454
37455             return isMatch;
37456         };
37457
37458         return this.filterBy(filterFn);
37459     },
37460
37461     /**
37462      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
37463      * The passed function will be called with each object in the collection.
37464      * If the function returns true, the value is included otherwise it is filtered.
37465      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
37466      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37467      * @return {Ext.util.MixedCollection} The new filtered collection
37468      */
37469     filterBy : function(fn, scope) {
37470         var me = this,
37471             newMC  = new this.self(),
37472             keys   = me.keys,
37473             items  = me.items,
37474             length = items.length,
37475             i;
37476
37477         newMC.getKey = me.getKey;
37478
37479         for (i = 0; i < length; i++) {
37480             if (fn.call(scope || me, items[i], keys[i])) {
37481                 newMC.add(keys[i], items[i]);
37482             }
37483         }
37484
37485         return newMC;
37486     },
37487
37488     /**
37489      * Finds the index of the first matching object in this collection by a specific property/value.
37490      * @param {String} property The name of a property on your objects.
37491      * @param {String/RegExp} value A string that the property values
37492      * should start with or a RegExp to test against the property.
37493      * @param {Number} [start=0] The index to start searching at.
37494      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
37495      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37496      * @return {Number} The matched index or -1
37497      */
37498     findIndex : function(property, value, start, anyMatch, caseSensitive){
37499         if(Ext.isEmpty(value, false)){
37500             return -1;
37501         }
37502         value = this.createValueMatcher(value, anyMatch, caseSensitive);
37503         return this.findIndexBy(function(o){
37504             return o && value.test(o[property]);
37505         }, null, start);
37506     },
37507
37508     /**
37509      * Find the index of the first matching object in this collection by a function.
37510      * If the function returns <i>true</i> it is considered a match.
37511      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
37512      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37513      * @param {Number} [start=0] The index to start searching at.
37514      * @return {Number} The matched index or -1
37515      */
37516     findIndexBy : function(fn, scope, start){
37517         var me = this,
37518             keys = me.keys,
37519             items = me.items,
37520             i = start || 0,
37521             len = items.length;
37522
37523         for (; i < len; i++) {
37524             if (fn.call(scope || me, items[i], keys[i])) {
37525                 return i;
37526             }
37527         }
37528         return -1;
37529     },
37530
37531     /**
37532      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
37533      * and by Ext.data.Store#filter
37534      * @private
37535      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
37536      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
37537      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
37538      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
37539      */
37540     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
37541         if (!value.exec) { // not a regex
37542             var er = Ext.String.escapeRegex;
37543             value = String(value);
37544
37545             if (anyMatch === true) {
37546                 value = er(value);
37547             } else {
37548                 value = '^' + er(value);
37549                 if (exactMatch === true) {
37550                     value += '$';
37551                 }
37552             }
37553             value = new RegExp(value, caseSensitive ? '' : 'i');
37554         }
37555         return value;
37556     },
37557
37558     /**
37559      * Creates a shallow copy of this collection
37560      * @return {Ext.util.MixedCollection}
37561      */
37562     clone : function() {
37563         var me = this,
37564             copy = new this.self(),
37565             keys = me.keys,
37566             items = me.items,
37567             i = 0,
37568             len = items.length;
37569
37570         for(; i < len; i++){
37571             copy.add(keys[i], items[i]);
37572         }
37573         copy.getKey = me.getKey;
37574         return copy;
37575     }
37576 });
37577
37578 /**
37579  * @docauthor Tommy Maintz <tommy@sencha.com>
37580  *
37581  * 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}.
37582  *
37583  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
37584  * is more likely you will want to use one of the component classes that import this mixin, such as
37585  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
37586  */
37587 Ext.define("Ext.util.Sortable", {
37588     /**
37589      * @property {Boolean} isSortable
37590      * Flag denoting that this object is sortable. Always true.
37591      */
37592     isSortable: true,
37593
37594     /**
37595      * @property {String} defaultSortDirection
37596      * The default sort direction to use if one is not specified.
37597      */
37598     defaultSortDirection: "ASC",
37599
37600     requires: [
37601         'Ext.util.Sorter'
37602     ],
37603
37604     /**
37605      * @property {String} sortRoot
37606      * The property in each item that contains the data to sort.
37607      */
37608
37609     /**
37610      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
37611      * own initialization.
37612      */
37613     initSortable: function() {
37614         var me = this,
37615             sorters = me.sorters;
37616
37617         /**
37618          * @property {Ext.util.MixedCollection} sorters
37619          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
37620          */
37621         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
37622             return item.id || item.property;
37623         });
37624
37625         if (sorters) {
37626             me.sorters.addAll(me.decodeSorters(sorters));
37627         }
37628     },
37629
37630     /**
37631      * Sorts the data in the Store by one or more of its properties. Example usage:
37632      *
37633      *     //sort by a single field
37634      *     myStore.sort('myField', 'DESC');
37635      *
37636      *     //sorting by multiple fields
37637      *     myStore.sort([
37638      *         {
37639      *             property : 'age',
37640      *             direction: 'ASC'
37641      *         },
37642      *         {
37643      *             property : 'name',
37644      *             direction: 'DESC'
37645      *         }
37646      *     ]);
37647      *
37648      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
37649      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
37650      *
37651      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
37652      *
37653      *     store.sort('myField');
37654      *     store.sort('myField');
37655      *
37656      * Is equivalent to this code, because Store handles the toggling automatically:
37657      *
37658      *     store.sort('myField', 'ASC');
37659      *     store.sort('myField', 'DESC');
37660      *
37661      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
37662      * {@link Ext.data.Model Model}, or an array of sorter configurations.
37663      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
37664      * @return {Ext.util.Sorter[]}
37665      */
37666     sort: function(sorters, direction, where, doSort) {
37667         var me = this,
37668             sorter, sorterFn,
37669             newSorters;
37670
37671         if (Ext.isArray(sorters)) {
37672             doSort = where;
37673             where = direction;
37674             newSorters = sorters;
37675         }
37676         else if (Ext.isObject(sorters)) {
37677             doSort = where;
37678             where = direction;
37679             newSorters = [sorters];
37680         }
37681         else if (Ext.isString(sorters)) {
37682             sorter = me.sorters.get(sorters);
37683
37684             if (!sorter) {
37685                 sorter = {
37686                     property : sorters,
37687                     direction: direction
37688                 };
37689                 newSorters = [sorter];
37690             }
37691             else if (direction === undefined) {
37692                 sorter.toggle();
37693             }
37694             else {
37695                 sorter.setDirection(direction);
37696             }
37697         }
37698
37699         if (newSorters && newSorters.length) {
37700             newSorters = me.decodeSorters(newSorters);
37701             if (Ext.isString(where)) {
37702                 if (where === 'prepend') {
37703                     sorters = me.sorters.clone().items;
37704
37705                     me.sorters.clear();
37706                     me.sorters.addAll(newSorters);
37707                     me.sorters.addAll(sorters);
37708                 }
37709                 else {
37710                     me.sorters.addAll(newSorters);
37711                 }
37712             }
37713             else {
37714                 me.sorters.clear();
37715                 me.sorters.addAll(newSorters);
37716             }
37717         }
37718
37719         if (doSort !== false) {
37720             me.onBeforeSort(newSorters);
37721             
37722             sorters = me.sorters.items;
37723             if (sorters.length) {
37724                 //construct an amalgamated sorter function which combines all of the Sorters passed
37725                 sorterFn = function(r1, r2) {
37726                     var result = sorters[0].sort(r1, r2),
37727                         length = sorters.length,
37728                         i;
37729
37730                         //if we have more than one sorter, OR any additional sorter functions together
37731                         for (i = 1; i < length; i++) {
37732                             result = result || sorters[i].sort.call(this, r1, r2);
37733                         }
37734
37735                     return result;
37736                 };
37737
37738                 me.doSort(sorterFn);
37739             }
37740         }
37741
37742         return sorters;
37743     },
37744
37745     onBeforeSort: Ext.emptyFn,
37746
37747     /**
37748      * @private
37749      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
37750      * @param {Object[]} sorters The sorters array
37751      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
37752      */
37753     decodeSorters: function(sorters) {
37754         if (!Ext.isArray(sorters)) {
37755             if (sorters === undefined) {
37756                 sorters = [];
37757             } else {
37758                 sorters = [sorters];
37759             }
37760         }
37761
37762         var length = sorters.length,
37763             Sorter = Ext.util.Sorter,
37764             fields = this.model ? this.model.prototype.fields : null,
37765             field,
37766             config, i;
37767
37768         for (i = 0; i < length; i++) {
37769             config = sorters[i];
37770
37771             if (!(config instanceof Sorter)) {
37772                 if (Ext.isString(config)) {
37773                     config = {
37774                         property: config
37775                     };
37776                 }
37777
37778                 Ext.applyIf(config, {
37779                     root     : this.sortRoot,
37780                     direction: "ASC"
37781                 });
37782
37783                 //support for 3.x style sorters where a function can be defined as 'fn'
37784                 if (config.fn) {
37785                     config.sorterFn = config.fn;
37786                 }
37787
37788                 //support a function to be passed as a sorter definition
37789                 if (typeof config == 'function') {
37790                     config = {
37791                         sorterFn: config
37792                     };
37793                 }
37794
37795                 // ensure sortType gets pushed on if necessary
37796                 if (fields && !config.transform) {
37797                     field = fields.get(config.property);
37798                     config.transform = field ? field.sortType : undefined;
37799                 }
37800                 sorters[i] = Ext.create('Ext.util.Sorter', config);
37801             }
37802         }
37803
37804         return sorters;
37805     },
37806
37807     getSorters: function() {
37808         return this.sorters.items;
37809     }
37810 });
37811 /**
37812  * @class Ext.util.MixedCollection
37813  * <p>
37814  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
37815  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
37816  * collection can be accessed by index  or via the key. Newly added items are added to
37817  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
37818  * is heavier and provides more functionality. Sample usage:
37819  * <pre><code>
37820 var coll = new Ext.util.MixedCollection();
37821 coll.add('key1', 'val1');
37822 coll.add('key2', 'val2');
37823 coll.add('key3', 'val3');
37824
37825 console.log(coll.get('key1')); // prints 'val1'
37826 console.log(coll.indexOfKey('key3')); // prints 2
37827  * </code></pre>
37828  *
37829  * <p>
37830  * The MixedCollection also has support for sorting and filtering of the values in the collection.
37831  * <pre><code>
37832 var coll = new Ext.util.MixedCollection();
37833 coll.add('key1', 100);
37834 coll.add('key2', -100);
37835 coll.add('key3', 17);
37836 coll.add('key4', 0);
37837 var biggerThanZero = coll.filterBy(function(value){
37838     return value > 0;
37839 });
37840 console.log(biggerThanZero.getCount()); // prints 2
37841  * </code></pre>
37842  * </p>
37843  */
37844 Ext.define('Ext.util.MixedCollection', {
37845     extend: 'Ext.util.AbstractMixedCollection',
37846     mixins: {
37847         sortable: 'Ext.util.Sortable'
37848     },
37849
37850     /**
37851      * Creates new MixedCollection.
37852      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
37853      * function should add function references to the collection. Defaults to
37854      * <tt>false</tt>.
37855      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
37856      * and return the key value for that item.  This is used when available to look up the key on items that
37857      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
37858      * equivalent to providing an implementation for the {@link #getKey} method.
37859      */
37860     constructor: function() {
37861         var me = this;
37862         me.callParent(arguments);
37863         me.addEvents('sort');
37864         me.mixins.sortable.initSortable.call(me);
37865     },
37866
37867     doSort: function(sorterFn) {
37868         this.sortBy(sorterFn);
37869     },
37870
37871     /**
37872      * @private
37873      * Performs the actual sorting based on a direction and a sorting function. Internally,
37874      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
37875      * the sorted array data back into this.items and this.keys
37876      * @param {String} property Property to sort by ('key', 'value', or 'index')
37877      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
37878      * @param {Function} fn (optional) Comparison function that defines the sort order.
37879      * Defaults to sorting by numeric value.
37880      */
37881     _sort : function(property, dir, fn){
37882         var me = this,
37883             i, len,
37884             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
37885
37886             //this is a temporary array used to apply the sorting function
37887             c     = [],
37888             keys  = me.keys,
37889             items = me.items;
37890
37891         //default to a simple sorter function if one is not provided
37892         fn = fn || function(a, b) {
37893             return a - b;
37894         };
37895
37896         //copy all the items into a temporary array, which we will sort
37897         for(i = 0, len = items.length; i < len; i++){
37898             c[c.length] = {
37899                 key  : keys[i],
37900                 value: items[i],
37901                 index: i
37902             };
37903         }
37904
37905         //sort the temporary array
37906         Ext.Array.sort(c, function(a, b){
37907             var v = fn(a[property], b[property]) * dsc;
37908             if(v === 0){
37909                 v = (a.index < b.index ? -1 : 1);
37910             }
37911             return v;
37912         });
37913
37914         //copy the temporary array back into the main this.items and this.keys objects
37915         for(i = 0, len = c.length; i < len; i++){
37916             items[i] = c[i].value;
37917             keys[i]  = c[i].key;
37918         }
37919
37920         me.fireEvent('sort', me);
37921     },
37922
37923     /**
37924      * Sorts the collection by a single sorter function
37925      * @param {Function} sorterFn The function to sort by
37926      */
37927     sortBy: function(sorterFn) {
37928         var me     = this,
37929             items  = me.items,
37930             keys   = me.keys,
37931             length = items.length,
37932             temp   = [],
37933             i;
37934
37935         //first we create a copy of the items array so that we can sort it
37936         for (i = 0; i < length; i++) {
37937             temp[i] = {
37938                 key  : keys[i],
37939                 value: items[i],
37940                 index: i
37941             };
37942         }
37943
37944         Ext.Array.sort(temp, function(a, b) {
37945             var v = sorterFn(a.value, b.value);
37946             if (v === 0) {
37947                 v = (a.index < b.index ? -1 : 1);
37948             }
37949
37950             return v;
37951         });
37952
37953         //copy the temporary array back into the main this.items and this.keys objects
37954         for (i = 0; i < length; i++) {
37955             items[i] = temp[i].value;
37956             keys[i]  = temp[i].key;
37957         }
37958         
37959         me.fireEvent('sort', me, items, keys);
37960     },
37961
37962     /**
37963      * Reorders each of the items based on a mapping from old index to new index. Internally this
37964      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
37965      * @param {Object} mapping Mapping from old item index to new item index
37966      */
37967     reorder: function(mapping) {
37968         var me = this,
37969             items = me.items,
37970             index = 0,
37971             length = items.length,
37972             order = [],
37973             remaining = [],
37974             oldIndex;
37975
37976         me.suspendEvents();
37977
37978         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
37979         for (oldIndex in mapping) {
37980             order[mapping[oldIndex]] = items[oldIndex];
37981         }
37982
37983         for (index = 0; index < length; index++) {
37984             if (mapping[index] == undefined) {
37985                 remaining.push(items[index]);
37986             }
37987         }
37988
37989         for (index = 0; index < length; index++) {
37990             if (order[index] == undefined) {
37991                 order[index] = remaining.shift();
37992             }
37993         }
37994
37995         me.clear();
37996         me.addAll(order);
37997
37998         me.resumeEvents();
37999         me.fireEvent('sort', me);
38000     },
38001
38002     /**
38003      * Sorts this collection by <b>key</b>s.
38004      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
38005      * @param {Function} fn (optional) Comparison function that defines the sort order.
38006      * Defaults to sorting by case insensitive string.
38007      */
38008     sortByKey : function(dir, fn){
38009         this._sort('key', dir, fn || function(a, b){
38010             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
38011             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
38012         });
38013     }
38014 });
38015
38016 /**
38017  * @author Ed Spencer
38018  * @class Ext.data.Errors
38019  * @extends Ext.util.MixedCollection
38020  *
38021  * <p>Wraps a collection of validation error responses and provides convenient functions for
38022  * accessing and errors for specific fields.</p>
38023  *
38024  * <p>Usually this class does not need to be instantiated directly - instances are instead created
38025  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
38026  *
38027 <pre><code>
38028 //validate some existing model instance - in this case it returned 2 failures messages
38029 var errors = myModel.validate();
38030
38031 errors.isValid(); //false
38032
38033 errors.length; //2
38034 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
38035 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
38036 </code></pre>
38037  */
38038 Ext.define('Ext.data.Errors', {
38039     extend: 'Ext.util.MixedCollection',
38040
38041     /**
38042      * Returns true if there are no errors in the collection
38043      * @return {Boolean}
38044      */
38045     isValid: function() {
38046         return this.length === 0;
38047     },
38048
38049     /**
38050      * Returns all of the errors for the given field
38051      * @param {String} fieldName The field to get errors for
38052      * @return {Object[]} All errors for the given field
38053      */
38054     getByField: function(fieldName) {
38055         var errors = [],
38056             error, field, i;
38057
38058         for (i = 0; i < this.length; i++) {
38059             error = this.items[i];
38060
38061             if (error.field == fieldName) {
38062                 errors.push(error);
38063             }
38064         }
38065
38066         return errors;
38067     }
38068 });
38069
38070 /**
38071  * @author Ed Spencer
38072  *
38073  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
38074  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
38075  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
38076  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
38077  * 
38078  *     Ext.create('Ext.data.Store', {
38079  *         model: 'User',
38080  *         proxy: {
38081  *             type: 'ajax',
38082  *             url : 'users.json',
38083  *             reader: {
38084  *                 type: 'json',
38085  *                 root: 'users'
38086  *             }
38087  *         },
38088  *     });
38089  *     
38090  * The above reader is configured to consume a JSON string that looks something like this:
38091  *  
38092  *     {
38093  *         "success": true,
38094  *         "users": [
38095  *             { "name": "User 1" },
38096  *             { "name": "User 2" }
38097  *         ]
38098  *     }
38099  * 
38100  *
38101  * # Loading Nested Data
38102  *
38103  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
38104  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
38105  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
38106  *
38107  *     Ext.define("User", {
38108  *         extend: 'Ext.data.Model',
38109  *         fields: [
38110  *             'id', 'name'
38111  *         ],
38112  *
38113  *         hasMany: {model: 'Order', name: 'orders'},
38114  *
38115  *         proxy: {
38116  *             type: 'rest',
38117  *             url : 'users.json',
38118  *             reader: {
38119  *                 type: 'json',
38120  *                 root: 'users'
38121  *             }
38122  *         }
38123  *     });
38124  *
38125  *     Ext.define("Order", {
38126  *         extend: 'Ext.data.Model',
38127  *         fields: [
38128  *             'id', 'total'
38129  *         ],
38130  *
38131  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
38132  *         belongsTo: 'User'
38133  *     });
38134  *
38135  *     Ext.define("OrderItem", {
38136  *         extend: 'Ext.data.Model',
38137  *         fields: [
38138  *             'id', 'price', 'quantity', 'order_id', 'product_id'
38139  *         ],
38140  *
38141  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
38142  *     });
38143  *
38144  *     Ext.define("Product", {
38145  *         extend: 'Ext.data.Model',
38146  *         fields: [
38147  *             'id', 'name'
38148  *         ],
38149  *
38150  *         hasMany: 'OrderItem'
38151  *     });
38152  *
38153  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
38154  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
38155  *
38156  *     {
38157  *         "users": [
38158  *             {
38159  *                 "id": 123,
38160  *                 "name": "Ed",
38161  *                 "orders": [
38162  *                     {
38163  *                         "id": 50,
38164  *                         "total": 100,
38165  *                         "order_items": [
38166  *                             {
38167  *                                 "id"      : 20,
38168  *                                 "price"   : 40,
38169  *                                 "quantity": 2,
38170  *                                 "product" : {
38171  *                                     "id": 1000,
38172  *                                     "name": "MacBook Pro"
38173  *                                 }
38174  *                             },
38175  *                             {
38176  *                                 "id"      : 21,
38177  *                                 "price"   : 20,
38178  *                                 "quantity": 3,
38179  *                                 "product" : {
38180  *                                     "id": 1001,
38181  *                                     "name": "iPhone"
38182  *                                 }
38183  *                             }
38184  *                         ]
38185  *                     }
38186  *                 ]
38187  *             }
38188  *         ]
38189  *     }
38190  *
38191  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
38192  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
38193  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
38194  *
38195  *     var store = Ext.create('Ext.data.Store', {
38196  *         model: "User"
38197  *     });
38198  *
38199  *     store.load({
38200  *         callback: function() {
38201  *             //the user that was loaded
38202  *             var user = store.first();
38203  *
38204  *             console.log("Orders for " + user.get('name') + ":")
38205  *
38206  *             //iterate over the Orders for each User
38207  *             user.orders().each(function(order) {
38208  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
38209  *
38210  *                 //iterate over the OrderItems for each Order
38211  *                 order.orderItems().each(function(orderItem) {
38212  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
38213  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
38214  *                     var product = orderItem.getProduct();
38215  *
38216  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
38217  *                 });
38218  *             });
38219  *         }
38220  *     });
38221  *
38222  * Running the code above results in the following:
38223  *
38224  *     Orders for Ed:
38225  *     Order ID: 50, which contains items:
38226  *     2 orders of MacBook Pro
38227  *     3 orders of iPhone
38228  */
38229 Ext.define('Ext.data.reader.Reader', {
38230     requires: ['Ext.data.ResultSet'],
38231     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
38232     
38233     /**
38234      * @cfg {String} idProperty
38235      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
38236      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
38237      */
38238
38239     /**
38240      * @cfg {String} totalProperty
38241      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
38242      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
38243      */
38244     totalProperty: 'total',
38245
38246     /**
38247      * @cfg {String} successProperty
38248      * Name of the property from which to retrieve the success attribute. Defaults to success. See
38249      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
38250      */
38251     successProperty: 'success',
38252
38253     /**
38254      * @cfg {String} root
38255      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
38256      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
38257      * 
38258      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
38259      *
38260      * The data packet value for this property should be an empty array to clear the data or show no data.
38261      */
38262     root: '',
38263     
38264     /**
38265      * @cfg {String} messageProperty
38266      * The name of the property which contains a response message. This property is optional.
38267      */
38268     
38269     /**
38270      * @cfg {Boolean} implicitIncludes
38271      * True to automatically parse models nested within other models in a response object. See the
38272      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
38273      */
38274     implicitIncludes: true,
38275     
38276     isReader: true,
38277     
38278     /**
38279      * Creates new Reader.
38280      * @param {Object} config (optional) Config object.
38281      */
38282     constructor: function(config) {
38283         var me = this;
38284         
38285         Ext.apply(me, config || {});
38286         me.fieldCount = 0;
38287         me.model = Ext.ModelManager.getModel(config.model);
38288         if (me.model) {
38289             me.buildExtractors();
38290         }
38291     },
38292
38293     /**
38294      * Sets a new model for the reader.
38295      * @private
38296      * @param {Object} model The model to set.
38297      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
38298      */
38299     setModel: function(model, setOnProxy) {
38300         var me = this;
38301         
38302         me.model = Ext.ModelManager.getModel(model);
38303         me.buildExtractors(true);
38304         
38305         if (setOnProxy && me.proxy) {
38306             me.proxy.setModel(me.model, true);
38307         }
38308     },
38309
38310     /**
38311      * Reads the given response object. This method normalizes the different types of response object that may be passed
38312      * to it, before handing off the reading of records to the {@link #readRecords} function.
38313      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
38314      * @return {Ext.data.ResultSet} The parsed ResultSet object
38315      */
38316     read: function(response) {
38317         var data = response;
38318         
38319         if (response && response.responseText) {
38320             data = this.getResponseData(response);
38321         }
38322         
38323         if (data) {
38324             return this.readRecords(data);
38325         } else {
38326             return this.nullResultSet;
38327         }
38328     },
38329
38330     /**
38331      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
38332      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
38333      * processing should not be needed.
38334      * @param {Object} data The raw data object
38335      * @return {Ext.data.ResultSet} A ResultSet object
38336      */
38337     readRecords: function(data) {
38338         var me  = this;
38339         
38340         /*
38341          * We check here whether the number of fields has changed since the last read.
38342          * This works around an issue when a Model is used for both a Tree and another
38343          * source, because the tree decorates the model with extra fields and it causes
38344          * issues because the readers aren't notified.
38345          */
38346         if (me.fieldCount !== me.getFields().length) {
38347             me.buildExtractors(true);
38348         }
38349         
38350         /**
38351          * @property {Object} rawData
38352          * The raw data object that was last passed to readRecords. Stored for further processing if needed
38353          */
38354         me.rawData = data;
38355
38356         data = me.getData(data);
38357
38358         // If we pass an array as the data, we dont use getRoot on the data.
38359         // Instead the root equals to the data.
38360         var root    = Ext.isArray(data) ? data : me.getRoot(data),
38361             success = true,
38362             recordCount = 0,
38363             total, value, records, message;
38364             
38365         if (root) {
38366             total = root.length;
38367         }
38368
38369         if (me.totalProperty) {
38370             value = parseInt(me.getTotal(data), 10);
38371             if (!isNaN(value)) {
38372                 total = value;
38373             }
38374         }
38375
38376         if (me.successProperty) {
38377             value = me.getSuccess(data);
38378             if (value === false || value === 'false') {
38379                 success = false;
38380             }
38381         }
38382         
38383         if (me.messageProperty) {
38384             message = me.getMessage(data);
38385         }
38386         
38387         if (root) {
38388             records = me.extractData(root);
38389             recordCount = records.length;
38390         } else {
38391             recordCount = 0;
38392             records = [];
38393         }
38394
38395         return Ext.create('Ext.data.ResultSet', {
38396             total  : total || recordCount,
38397             count  : recordCount,
38398             records: records,
38399             success: success,
38400             message: message
38401         });
38402     },
38403
38404     /**
38405      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
38406      * @param {Object[]/Object} root from server response
38407      * @private
38408      */
38409     extractData : function(root) {
38410         var me = this,
38411             values  = [],
38412             records = [],
38413             Model   = me.model,
38414             i       = 0,
38415             length  = root.length,
38416             idProp  = me.getIdProperty(),
38417             node, id, record;
38418             
38419         if (!root.length && Ext.isObject(root)) {
38420             root = [root];
38421             length = 1;
38422         }
38423
38424         for (; i < length; i++) {
38425             node   = root[i];
38426             values = me.extractValues(node);
38427             id     = me.getId(node);
38428
38429             
38430             record = new Model(values, id, node);
38431             records.push(record);
38432                 
38433             if (me.implicitIncludes) {
38434                 me.readAssociated(record, node);
38435             }
38436         }
38437
38438         return records;
38439     },
38440     
38441     /**
38442      * @private
38443      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
38444      * on the record provided.
38445      * @param {Ext.data.Model} record The record to load associations for
38446      * @param {Object} data The data object
38447      * @return {String} Return value description
38448      */
38449     readAssociated: function(record, data) {
38450         var associations = record.associations.items,
38451             i            = 0,
38452             length       = associations.length,
38453             association, associationData, proxy, reader;
38454         
38455         for (; i < length; i++) {
38456             association     = associations[i];
38457             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
38458             
38459             if (associationData) {
38460                 reader = association.getReader();
38461                 if (!reader) {
38462                     proxy = association.associatedModel.proxy;
38463                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
38464                     if (proxy) {
38465                         reader = proxy.getReader();
38466                     } else {
38467                         reader = new this.constructor({
38468                             model: association.associatedName
38469                         });
38470                     }
38471                 }
38472                 association.read(record, reader, associationData);
38473             }  
38474         }
38475     },
38476     
38477     /**
38478      * @private
38479      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
38480      * record, this should return the relevant part of that data for the given association name. This is only really
38481      * needed to support the XML Reader, which has to do a query to get the associated data object
38482      * @param {Object} data The raw data object
38483      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
38484      * @return {Object} The root
38485      */
38486     getAssociatedDataRoot: function(data, associationName) {
38487         return data[associationName];
38488     },
38489     
38490     getFields: function() {
38491         return this.model.prototype.fields.items;
38492     },
38493
38494     /**
38495      * @private
38496      * Given an object representing a single model instance's data, iterates over the model's fields and
38497      * builds an object with the value for each field.
38498      * @param {Object} data The data object to convert
38499      * @return {Object} Data object suitable for use with a model constructor
38500      */
38501     extractValues: function(data) {
38502         var fields = this.getFields(),
38503             i      = 0,
38504             length = fields.length,
38505             output = {},
38506             field, value;
38507
38508         for (; i < length; i++) {
38509             field = fields[i];
38510             value = this.extractorFunctions[i](data);
38511
38512             output[field.name] = value;
38513         }
38514
38515         return output;
38516     },
38517
38518     /**
38519      * @private
38520      * By default this function just returns what is passed to it. It can be overridden in a subclass
38521      * to return something else. See XmlReader for an example.
38522      * @param {Object} data The data object
38523      * @return {Object} The normalized data object
38524      */
38525     getData: function(data) {
38526         return data;
38527     },
38528
38529     /**
38530      * @private
38531      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
38532      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
38533      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
38534      * @param {Object} data The data object
38535      * @return {Object} The same data object
38536      */
38537     getRoot: function(data) {
38538         return data;
38539     },
38540
38541     /**
38542      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
38543      * implemented by each subclass
38544      * @param {Object} response The responce object
38545      * @return {Object} The useful data from the response
38546      */
38547     getResponseData: function(response) {
38548     },
38549
38550     /**
38551      * @private
38552      * Reconfigures the meta data tied to this Reader
38553      */
38554     onMetaChange : function(meta) {
38555         var fields = meta.fields,
38556             newModel;
38557         
38558         Ext.apply(this, meta);
38559         
38560         if (fields) {
38561             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
38562                 extend: 'Ext.data.Model',
38563                 fields: fields
38564             });
38565             this.setModel(newModel, true);
38566         } else {
38567             this.buildExtractors(true);
38568         }
38569     },
38570     
38571     /**
38572      * Get the idProperty to use for extracting data
38573      * @private
38574      * @return {String} The id property
38575      */
38576     getIdProperty: function(){
38577         var prop = this.idProperty;
38578         if (Ext.isEmpty(prop)) {
38579             prop = this.model.prototype.idProperty;
38580         }
38581         return prop;
38582     },
38583
38584     /**
38585      * @private
38586      * This builds optimized functions for retrieving record data and meta data from an object.
38587      * Subclasses may need to implement their own getRoot function.
38588      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
38589      */
38590     buildExtractors: function(force) {
38591         var me          = this,
38592             idProp      = me.getIdProperty(),
38593             totalProp   = me.totalProperty,
38594             successProp = me.successProperty,
38595             messageProp = me.messageProperty,
38596             accessor;
38597             
38598         if (force === true) {
38599             delete me.extractorFunctions;
38600         }
38601         
38602         if (me.extractorFunctions) {
38603             return;
38604         }   
38605
38606         //build the extractors for all the meta data
38607         if (totalProp) {
38608             me.getTotal = me.createAccessor(totalProp);
38609         }
38610
38611         if (successProp) {
38612             me.getSuccess = me.createAccessor(successProp);
38613         }
38614
38615         if (messageProp) {
38616             me.getMessage = me.createAccessor(messageProp);
38617         }
38618
38619         if (idProp) {
38620             accessor = me.createAccessor(idProp);
38621
38622             me.getId = function(record) {
38623                 var id = accessor.call(me, record);
38624                 return (id === undefined || id === '') ? null : id;
38625             };
38626         } else {
38627             me.getId = function() {
38628                 return null;
38629             };
38630         }
38631         me.buildFieldExtractors();
38632     },
38633
38634     /**
38635      * @private
38636      */
38637     buildFieldExtractors: function() {
38638         //now build the extractors for all the fields
38639         var me = this,
38640             fields = me.getFields(),
38641             ln = fields.length,
38642             i  = 0,
38643             extractorFunctions = [],
38644             field, map;
38645
38646         for (; i < ln; i++) {
38647             field = fields[i];
38648             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
38649
38650             extractorFunctions.push(me.createAccessor(map));
38651         }
38652         me.fieldCount = ln;
38653
38654         me.extractorFunctions = extractorFunctions;
38655     }
38656 }, function() {
38657     Ext.apply(this, {
38658         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
38659         nullResultSet: Ext.create('Ext.data.ResultSet', {
38660             total  : 0,
38661             count  : 0,
38662             records: [],
38663             success: true
38664         })
38665     });
38666 });
38667 /**
38668  * @author Ed Spencer
38669  * @class Ext.data.reader.Json
38670  * @extends Ext.data.reader.Reader
38671  *
38672  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
38673  * happens as a result of loading a Store - for example we might create something like this:</p>
38674  *
38675 <pre><code>
38676 Ext.define('User', {
38677     extend: 'Ext.data.Model',
38678     fields: ['id', 'name', 'email']
38679 });
38680
38681 var store = Ext.create('Ext.data.Store', {
38682     model: 'User',
38683     proxy: {
38684         type: 'ajax',
38685         url : 'users.json',
38686         reader: {
38687             type: 'json'
38688         }
38689     }
38690 });
38691 </code></pre>
38692  *
38693  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
38694  * not already familiar with them.</p>
38695  *
38696  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
38697  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
38698  * Store, so it is as if we passed this instead:
38699  *
38700 <pre><code>
38701 reader: {
38702     type : 'json',
38703     model: 'User'
38704 }
38705 </code></pre>
38706  *
38707  * <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>
38708  *
38709 <pre><code>
38710 [
38711     {
38712         "id": 1,
38713         "name": "Ed Spencer",
38714         "email": "ed@sencha.com"
38715     },
38716     {
38717         "id": 2,
38718         "name": "Abe Elias",
38719         "email": "abe@sencha.com"
38720     }
38721 ]
38722 </code></pre>
38723  *
38724  * <p><u>Reading other JSON formats</u></p>
38725  *
38726  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
38727  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
38728  * {@link #root} configuration to parse data that comes back like this:</p>
38729  *
38730 <pre><code>
38731 {
38732     "users": [
38733        {
38734            "id": 1,
38735            "name": "Ed Spencer",
38736            "email": "ed@sencha.com"
38737        },
38738        {
38739            "id": 2,
38740            "name": "Abe Elias",
38741            "email": "abe@sencha.com"
38742        }
38743     ]
38744 }
38745 </code></pre>
38746  *
38747  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
38748  *
38749 <pre><code>
38750 reader: {
38751     type: 'json',
38752     root: 'users'
38753 }
38754 </code></pre>
38755  *
38756  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
38757  * around each record inside a nested structure like this:</p>
38758  *
38759 <pre><code>
38760 {
38761     "total": 122,
38762     "offset": 0,
38763     "users": [
38764         {
38765             "id": "ed-spencer-1",
38766             "value": 1,
38767             "user": {
38768                 "id": 1,
38769                 "name": "Ed Spencer",
38770                 "email": "ed@sencha.com"
38771             }
38772         }
38773     ]
38774 }
38775 </code></pre>
38776  *
38777  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
38778  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
38779  * JSON above we need to specify the {@link #record} configuration like this:</p>
38780  *
38781 <pre><code>
38782 reader: {
38783     type  : 'json',
38784     root  : 'users',
38785     record: 'user'
38786 }
38787 </code></pre>
38788  *
38789  * <p><u>Response metadata</u></p>
38790  *
38791  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
38792  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
38793  * like this:</p>
38794  *
38795 <pre><code>
38796 {
38797     "total": 100,
38798     "success": true,
38799     "users": [
38800         {
38801             "id": 1,
38802             "name": "Ed Spencer",
38803             "email": "ed@sencha.com"
38804         }
38805     ]
38806 }
38807 </code></pre>
38808  *
38809  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
38810  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
38811  * options:</p>
38812  *
38813 <pre><code>
38814 reader: {
38815     type : 'json',
38816     root : 'users',
38817     totalProperty  : 'total',
38818     successProperty: 'success'
38819 }
38820 </code></pre>
38821  *
38822  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
38823  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
38824  * returned.</p>
38825  */
38826 Ext.define('Ext.data.reader.Json', {
38827     extend: 'Ext.data.reader.Reader',
38828     alternateClassName: 'Ext.data.JsonReader',
38829     alias : 'reader.json',
38830
38831     root: '',
38832
38833     /**
38834      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
38835      * See the JsonReader intro docs for more details. This is not often needed.
38836      */
38837
38838     /**
38839      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
38840      * reading values. Defalts to <tt>false</tt>.
38841      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
38842      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
38843      * "foo.bar.baz" direct from the root object.
38844      */
38845     useSimpleAccessors: false,
38846
38847     /**
38848      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
38849      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
38850      * @param {Object} data The raw JSON data
38851      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
38852      */
38853     readRecords: function(data) {
38854         //this has to be before the call to super because we use the meta data in the superclass readRecords
38855         if (data.metaData) {
38856             this.onMetaChange(data.metaData);
38857         }
38858
38859         /**
38860          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
38861          * @property {Object} jsonData
38862          */
38863         this.jsonData = data;
38864         return this.callParent([data]);
38865     },
38866
38867     //inherit docs
38868     getResponseData: function(response) {
38869         var data;
38870         try {
38871             data = Ext.decode(response.responseText);
38872         }
38873         catch (ex) {
38874             Ext.Error.raise({
38875                 response: response,
38876                 json: response.responseText,
38877                 parseError: ex,
38878                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
38879             });
38880         }
38881
38882         return data;
38883     },
38884
38885     //inherit docs
38886     buildExtractors : function() {
38887         var me = this;
38888
38889         me.callParent(arguments);
38890
38891         if (me.root) {
38892             me.getRoot = me.createAccessor(me.root);
38893         } else {
38894             me.getRoot = function(root) {
38895                 return root;
38896             };
38897         }
38898     },
38899
38900     /**
38901      * @private
38902      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
38903      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
38904      * @param {Object} root The JSON root node
38905      * @return {Ext.data.Model[]} The records
38906      */
38907     extractData: function(root) {
38908         var recordName = this.record,
38909             data = [],
38910             length, i;
38911
38912         if (recordName) {
38913             length = root.length;
38914             
38915             if (!length && Ext.isObject(root)) {
38916                 length = 1;
38917                 root = [root];
38918             }
38919
38920             for (i = 0; i < length; i++) {
38921                 data[i] = root[i][recordName];
38922             }
38923         } else {
38924             data = root;
38925         }
38926         return this.callParent([data]);
38927     },
38928
38929     /**
38930      * @private
38931      * Returns an accessor function for the given property string. Gives support for properties such as the following:
38932      * 'someProperty'
38933      * 'some.property'
38934      * 'some["property"]'
38935      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
38936      */
38937     createAccessor: function() {
38938         var re = /[\[\.]/;
38939
38940         return function(expr) {
38941             if (Ext.isEmpty(expr)) {
38942                 return Ext.emptyFn;
38943             }
38944             if (Ext.isFunction(expr)) {
38945                 return expr;
38946             }
38947             if (this.useSimpleAccessors !== true) {
38948                 var i = String(expr).search(re);
38949                 if (i >= 0) {
38950                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
38951                 }
38952             }
38953             return function(obj) {
38954                 return obj[expr];
38955             };
38956         };
38957     }()
38958 });
38959 /**
38960  * @class Ext.data.writer.Json
38961  * @extends Ext.data.writer.Writer
38962
38963 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
38964 The {@link #allowSingle} configuration can be set to false to force the records to always be
38965 encoded in an array, even if there is only a single record being sent.
38966
38967  * @markdown
38968  */
38969 Ext.define('Ext.data.writer.Json', {
38970     extend: 'Ext.data.writer.Writer',
38971     alternateClassName: 'Ext.data.JsonWriter',
38972     alias: 'writer.json',
38973     
38974     /**
38975      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
38976      * Example generated request, using root: 'records':
38977 <pre><code>
38978 {'records': [{name: 'my record'}, {name: 'another record'}]}
38979 </code></pre>
38980      */
38981     root: undefined,
38982     
38983     /**
38984      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
38985      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
38986      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
38987      * sent to the server.
38988      */
38989     encode: false,
38990     
38991     /**
38992      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
38993      * one record being sent. When there is more than one record, they will always be encoded into an array.
38994      * Defaults to <tt>true</tt>. Example:
38995      * <pre><code>
38996 // with allowSingle: true
38997 "root": {
38998     "first": "Mark",
38999     "last": "Corrigan"
39000 }
39001
39002 // with allowSingle: false
39003 "root": [{
39004     "first": "Mark",
39005     "last": "Corrigan"
39006 }]
39007      * </code></pre>
39008      */
39009     allowSingle: true,
39010     
39011     //inherit docs
39012     writeRecords: function(request, data) {
39013         var root = this.root;
39014         
39015         if (this.allowSingle && data.length == 1) {
39016             // convert to single object format
39017             data = data[0];
39018         }
39019         
39020         if (this.encode) {
39021             if (root) {
39022                 // sending as a param, need to encode
39023                 request.params[root] = Ext.encode(data);
39024             } else {
39025             }
39026         } else {
39027             // send as jsonData
39028             request.jsonData = request.jsonData || {};
39029             if (root) {
39030                 request.jsonData[root] = data;
39031             } else {
39032                 request.jsonData = data;
39033             }
39034         }
39035         return request;
39036     }
39037 });
39038
39039 /**
39040  * @author Ed Spencer
39041  *
39042  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
39043  * data. Usually developers will not need to create or interact with proxies directly.
39044  *
39045  * # Types of Proxy
39046  *
39047  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
39048  * The Client proxies save their data locally and include the following subclasses:
39049  *
39050  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
39051  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
39052  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
39053  *
39054  * The Server proxies save their data by sending requests to some remote server. These proxies include:
39055  *
39056  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
39057  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
39058  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
39059  *
39060  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
39061  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
39062  * respectively. Each Proxy subclass implements these functions.
39063  *
39064  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
39065  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
39066  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
39067  * method also accepts a callback function to be called asynchronously on completion.
39068  *
39069  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
39070  * method.
39071  */
39072 Ext.define('Ext.data.proxy.Proxy', {
39073     alias: 'proxy.proxy',
39074     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
39075     requires: [
39076         'Ext.data.reader.Json',
39077         'Ext.data.writer.Json'
39078     ],
39079     uses: [
39080         'Ext.data.Batch', 
39081         'Ext.data.Operation', 
39082         'Ext.data.Model'
39083     ],
39084     mixins: {
39085         observable: 'Ext.util.Observable'
39086     },
39087     
39088     /**
39089      * @cfg {String} batchOrder
39090      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
39091      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
39092      */
39093     batchOrder: 'create,update,destroy',
39094     
39095     /**
39096      * @cfg {Boolean} batchActions
39097      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
39098      */
39099     batchActions: true,
39100     
39101     /**
39102      * @cfg {String} defaultReaderType
39103      * The default registered reader type. Defaults to 'json'.
39104      * @private
39105      */
39106     defaultReaderType: 'json',
39107     
39108     /**
39109      * @cfg {String} defaultWriterType
39110      * The default registered writer type. Defaults to 'json'.
39111      * @private
39112      */
39113     defaultWriterType: 'json',
39114     
39115     /**
39116      * @cfg {String/Ext.data.Model} model
39117      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
39118      * Model constructor. Required.
39119      */
39120     
39121     /**
39122      * @cfg {Object/String/Ext.data.reader.Reader} reader
39123      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
39124      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
39125      */
39126     
39127     /**
39128      * @cfg {Object/String/Ext.data.writer.Writer} writer
39129      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
39130      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
39131      */
39132     
39133     isProxy: true,
39134     
39135     /**
39136      * Creates the Proxy
39137      * @param {Object} config (optional) Config object.
39138      */
39139     constructor: function(config) {
39140         config = config || {};
39141         
39142         if (config.model === undefined) {
39143             delete config.model;
39144         }
39145
39146         this.mixins.observable.constructor.call(this, config);
39147         
39148         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
39149             this.setModel(this.model);
39150         }
39151     },
39152     
39153     /**
39154      * Sets the model associated with this proxy. This will only usually be called by a Store
39155      *
39156      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
39157      * or a reference to the model's constructor
39158      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
39159      */
39160     setModel: function(model, setOnStore) {
39161         this.model = Ext.ModelManager.getModel(model);
39162         
39163         var reader = this.reader,
39164             writer = this.writer;
39165         
39166         this.setReader(reader);
39167         this.setWriter(writer);
39168         
39169         if (setOnStore && this.store) {
39170             this.store.setModel(this.model);
39171         }
39172     },
39173     
39174     /**
39175      * Returns the model attached to this Proxy
39176      * @return {Ext.data.Model} The model
39177      */
39178     getModel: function() {
39179         return this.model;
39180     },
39181     
39182     /**
39183      * Sets the Proxy's Reader by string, config object or Reader instance
39184      *
39185      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
39186      * a configuration object or an Ext.data.reader.Reader instance
39187      * @return {Ext.data.reader.Reader} The attached Reader object
39188      */
39189     setReader: function(reader) {
39190         var me = this;
39191         
39192         if (reader === undefined || typeof reader == 'string') {
39193             reader = {
39194                 type: reader
39195             };
39196         }
39197
39198         if (reader.isReader) {
39199             reader.setModel(me.model);
39200         } else {
39201             Ext.applyIf(reader, {
39202                 proxy: me,
39203                 model: me.model,
39204                 type : me.defaultReaderType
39205             });
39206
39207             reader = Ext.createByAlias('reader.' + reader.type, reader);
39208         }
39209         
39210         me.reader = reader;
39211         return me.reader;
39212     },
39213     
39214     /**
39215      * Returns the reader currently attached to this proxy instance
39216      * @return {Ext.data.reader.Reader} The Reader instance
39217      */
39218     getReader: function() {
39219         return this.reader;
39220     },
39221     
39222     /**
39223      * Sets the Proxy's Writer by string, config object or Writer instance
39224      *
39225      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
39226      * a configuration object or an Ext.data.writer.Writer instance
39227      * @return {Ext.data.writer.Writer} The attached Writer object
39228      */
39229     setWriter: function(writer) {
39230         if (writer === undefined || typeof writer == 'string') {
39231             writer = {
39232                 type: writer
39233             };
39234         }
39235
39236         if (!(writer instanceof Ext.data.writer.Writer)) {
39237             Ext.applyIf(writer, {
39238                 model: this.model,
39239                 type : this.defaultWriterType
39240             });
39241
39242             writer = Ext.createByAlias('writer.' + writer.type, writer);
39243         }
39244         
39245         this.writer = writer;
39246         
39247         return this.writer;
39248     },
39249     
39250     /**
39251      * Returns the writer currently attached to this proxy instance
39252      * @return {Ext.data.writer.Writer} The Writer instance
39253      */
39254     getWriter: function() {
39255         return this.writer;
39256     },
39257     
39258     /**
39259      * Performs the given create operation.
39260      * @param {Ext.data.Operation} operation The Operation to perform
39261      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39262      * @param {Object} scope Scope to execute the callback function in
39263      * @method
39264      */
39265     create: Ext.emptyFn,
39266     
39267     /**
39268      * Performs the given read operation.
39269      * @param {Ext.data.Operation} operation The Operation to perform
39270      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39271      * @param {Object} scope Scope to execute the callback function in
39272      * @method
39273      */
39274     read: Ext.emptyFn,
39275     
39276     /**
39277      * Performs the given update operation.
39278      * @param {Ext.data.Operation} operation The Operation to perform
39279      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39280      * @param {Object} scope Scope to execute the callback function in
39281      * @method
39282      */
39283     update: Ext.emptyFn,
39284     
39285     /**
39286      * Performs the given destroy operation.
39287      * @param {Ext.data.Operation} operation The Operation to perform
39288      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39289      * @param {Object} scope Scope to execute the callback function in
39290      * @method
39291      */
39292     destroy: Ext.emptyFn,
39293     
39294     /**
39295      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
39296      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
39297      *
39298      *     myProxy.batch({
39299      *         create : [myModel1, myModel2],
39300      *         update : [myModel3],
39301      *         destroy: [myModel4, myModel5]
39302      *     });
39303      *
39304      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
39305      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
39306      * saved but should now be destroyed.
39307      *
39308      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
39309      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
39310      * see {@link Ext.data.Batch}
39311      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
39312      */
39313     batch: function(operations, listeners) {
39314         var me = this,
39315             batch = Ext.create('Ext.data.Batch', {
39316                 proxy: me,
39317                 listeners: listeners || {}
39318             }),
39319             useBatch = me.batchActions, 
39320             records;
39321         
39322         Ext.each(me.batchOrder.split(','), function(action) {
39323             records = operations[action];
39324             if (records) {
39325                 if (useBatch) {
39326                     batch.add(Ext.create('Ext.data.Operation', {
39327                         action: action,
39328                         records: records
39329                     }));
39330                 } else {
39331                     Ext.each(records, function(record){
39332                         batch.add(Ext.create('Ext.data.Operation', {
39333                             action : action, 
39334                             records: [record]
39335                         }));
39336                     });
39337                 }
39338             }
39339         }, me);
39340         
39341         batch.start();
39342         return batch;
39343     }
39344 }, function() {
39345     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
39346     
39347     //backwards compatibility
39348     Ext.data.DataProxy = this;
39349     // Ext.deprecate('platform', '2.0', function() {
39350     //     Ext.data.DataProxy = this;
39351     // }, this);
39352 });
39353
39354 /**
39355  * @author Ed Spencer
39356  *
39357  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
39358  * would not usually be used directly.
39359  *
39360  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
39361  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
39362  * an alias of AjaxProxy).
39363  * @private
39364  */
39365 Ext.define('Ext.data.proxy.Server', {
39366     extend: 'Ext.data.proxy.Proxy',
39367     alias : 'proxy.server',
39368     alternateClassName: 'Ext.data.ServerProxy',
39369     uses  : ['Ext.data.Request'],
39370
39371     /**
39372      * @cfg {String} url
39373      * The URL from which to request the data object.
39374      */
39375
39376     /**
39377      * @cfg {String} pageParam
39378      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
39379      * want to send a page parameter.
39380      */
39381     pageParam: 'page',
39382
39383     /**
39384      * @cfg {String} startParam
39385      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
39386      * want to send a start parameter.
39387      */
39388     startParam: 'start',
39389
39390     /**
39391      * @cfg {String} limitParam
39392      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
39393      * want to send a limit parameter.
39394      */
39395     limitParam: 'limit',
39396
39397     /**
39398      * @cfg {String} groupParam
39399      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
39400      * want to send a group parameter.
39401      */
39402     groupParam: 'group',
39403
39404     /**
39405      * @cfg {String} sortParam
39406      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
39407      * want to send a sort parameter.
39408      */
39409     sortParam: 'sort',
39410
39411     /**
39412      * @cfg {String} filterParam
39413      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
39414      * want to send a filter parameter.
39415      */
39416     filterParam: 'filter',
39417
39418     /**
39419      * @cfg {String} directionParam
39420      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
39421      * true.** Defaults to 'dir'.
39422      */
39423     directionParam: 'dir',
39424
39425     /**
39426      * @cfg {Boolean} simpleSortMode
39427      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
39428      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
39429      * or 'DESC'.
39430      */
39431     simpleSortMode: false,
39432
39433     /**
39434      * @cfg {Boolean} noCache
39435      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
39436      */
39437     noCache : true,
39438
39439     /**
39440      * @cfg {String} cacheString
39441      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
39442      */
39443     cacheString: "_dc",
39444
39445     /**
39446      * @cfg {Number} timeout
39447      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
39448      */
39449     timeout : 30000,
39450
39451     /**
39452      * @cfg {Object} api
39453      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
39454      *
39455      *     api: {
39456      *         create  : undefined,
39457      *         read    : undefined,
39458      *         update  : undefined,
39459      *         destroy : undefined
39460      *     }
39461      *
39462      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
39463      * {@link #api} property, or if undefined default to the configured
39464      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
39465      *
39466      * For example:
39467      *
39468      *     api: {
39469      *         create  : '/controller/new',
39470      *         read    : '/controller/load',
39471      *         update  : '/controller/update',
39472      *         destroy : '/controller/destroy_action'
39473      *     }
39474      *
39475      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
39476      * configured {@link Ext.data.proxy.Server#url url}.
39477      */
39478
39479     constructor: function(config) {
39480         var me = this;
39481
39482         config = config || {};
39483         this.addEvents(
39484             /**
39485              * @event exception
39486              * Fires when the server returns an exception
39487              * @param {Ext.data.proxy.Proxy} this
39488              * @param {Object} response The response from the AJAX request
39489              * @param {Ext.data.Operation} operation The operation that triggered request
39490              */
39491             'exception'
39492         );
39493         me.callParent([config]);
39494
39495         /**
39496          * @cfg {Object} extraParams
39497          * Extra parameters that will be included on every request. Individual requests with params of the same name
39498          * will override these params when they are in conflict.
39499          */
39500         me.extraParams = config.extraParams || {};
39501
39502         me.api = config.api || {};
39503
39504         //backwards compatibility, will be deprecated in 5.0
39505         me.nocache = me.noCache;
39506     },
39507
39508     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
39509     create: function() {
39510         return this.doRequest.apply(this, arguments);
39511     },
39512
39513     read: function() {
39514         return this.doRequest.apply(this, arguments);
39515     },
39516
39517     update: function() {
39518         return this.doRequest.apply(this, arguments);
39519     },
39520
39521     destroy: function() {
39522         return this.doRequest.apply(this, arguments);
39523     },
39524
39525     /**
39526      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
39527      * that this Proxy is attached to.
39528      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
39529      * @return {Ext.data.Request} The request object
39530      */
39531     buildRequest: function(operation) {
39532         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
39533             request;
39534
39535         //copy any sorters, filters etc into the params so they can be sent over the wire
39536         params = Ext.applyIf(params, this.getParams(operation));
39537
39538         if (operation.id && !params.id) {
39539             params.id = operation.id;
39540         }
39541
39542         request = Ext.create('Ext.data.Request', {
39543             params   : params,
39544             action   : operation.action,
39545             records  : operation.records,
39546             operation: operation,
39547             url      : operation.url
39548         });
39549
39550         request.url = this.buildUrl(request);
39551
39552         /*
39553          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
39554          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
39555          */
39556         operation.request = request;
39557
39558         return request;
39559     },
39560
39561     // Should this be documented as protected method?
39562     processResponse: function(success, operation, request, response, callback, scope){
39563         var me = this,
39564             reader,
39565             result;
39566
39567         if (success === true) {
39568             reader = me.getReader();
39569             result = reader.read(me.extractResponseData(response));
39570
39571             if (result.success !== false) {
39572                 //see comment in buildRequest for why we include the response object here
39573                 Ext.apply(operation, {
39574                     response: response,
39575                     resultSet: result
39576                 });
39577
39578                 operation.commitRecords(result.records);
39579                 operation.setCompleted();
39580                 operation.setSuccessful();
39581             } else {
39582                 operation.setException(result.message);
39583                 me.fireEvent('exception', this, response, operation);
39584             }
39585         } else {
39586             me.setException(operation, response);
39587             me.fireEvent('exception', this, response, operation);
39588         }
39589
39590         //this callback is the one that was passed to the 'read' or 'write' function above
39591         if (typeof callback == 'function') {
39592             callback.call(scope || me, operation);
39593         }
39594
39595         me.afterRequest(request, success);
39596     },
39597
39598     /**
39599      * Sets up an exception on the operation
39600      * @private
39601      * @param {Ext.data.Operation} operation The operation
39602      * @param {Object} response The response
39603      */
39604     setException: function(operation, response){
39605         operation.setException({
39606             status: response.status,
39607             statusText: response.statusText
39608         });
39609     },
39610
39611     /**
39612      * Template method to allow subclasses to specify how to get the response for the reader.
39613      * @template
39614      * @private
39615      * @param {Object} response The server response
39616      * @return {Object} The response data to be used by the reader
39617      */
39618     extractResponseData: function(response){
39619         return response;
39620     },
39621
39622     /**
39623      * Encode any values being sent to the server. Can be overridden in subclasses.
39624      * @private
39625      * @param {Array} An array of sorters/filters.
39626      * @return {Object} The encoded value
39627      */
39628     applyEncoding: function(value){
39629         return Ext.encode(value);
39630     },
39631
39632     /**
39633      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
39634      * this simply JSON-encodes the sorter data
39635      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
39636      * @return {String} The encoded sorters
39637      */
39638     encodeSorters: function(sorters) {
39639         var min = [],
39640             length = sorters.length,
39641             i = 0;
39642
39643         for (; i < length; i++) {
39644             min[i] = {
39645                 property : sorters[i].property,
39646                 direction: sorters[i].direction
39647             };
39648         }
39649         return this.applyEncoding(min);
39650
39651     },
39652
39653     /**
39654      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
39655      * this simply JSON-encodes the filter data
39656      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
39657      * @return {String} The encoded filters
39658      */
39659     encodeFilters: function(filters) {
39660         var min = [],
39661             length = filters.length,
39662             i = 0;
39663
39664         for (; i < length; i++) {
39665             min[i] = {
39666                 property: filters[i].property,
39667                 value   : filters[i].value
39668             };
39669         }
39670         return this.applyEncoding(min);
39671     },
39672
39673     /**
39674      * @private
39675      * Copy any sorters, filters etc into the params so they can be sent over the wire
39676      */
39677     getParams: function(operation) {
39678         var me             = this,
39679             params         = {},
39680             isDef          = Ext.isDefined,
39681             groupers       = operation.groupers,
39682             sorters        = operation.sorters,
39683             filters        = operation.filters,
39684             page           = operation.page,
39685             start          = operation.start,
39686             limit          = operation.limit,
39687
39688             simpleSortMode = me.simpleSortMode,
39689
39690             pageParam      = me.pageParam,
39691             startParam     = me.startParam,
39692             limitParam     = me.limitParam,
39693             groupParam     = me.groupParam,
39694             sortParam      = me.sortParam,
39695             filterParam    = me.filterParam,
39696             directionParam = me.directionParam;
39697
39698         if (pageParam && isDef(page)) {
39699             params[pageParam] = page;
39700         }
39701
39702         if (startParam && isDef(start)) {
39703             params[startParam] = start;
39704         }
39705
39706         if (limitParam && isDef(limit)) {
39707             params[limitParam] = limit;
39708         }
39709
39710         if (groupParam && groupers && groupers.length > 0) {
39711             // Grouper is a subclass of sorter, so we can just use the sorter method
39712             params[groupParam] = me.encodeSorters(groupers);
39713         }
39714
39715         if (sortParam && sorters && sorters.length > 0) {
39716             if (simpleSortMode) {
39717                 params[sortParam] = sorters[0].property;
39718                 params[directionParam] = sorters[0].direction;
39719             } else {
39720                 params[sortParam] = me.encodeSorters(sorters);
39721             }
39722
39723         }
39724
39725         if (filterParam && filters && filters.length > 0) {
39726             params[filterParam] = me.encodeFilters(filters);
39727         }
39728
39729         return params;
39730     },
39731
39732     /**
39733      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
39734      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
39735      * @param {Ext.data.Request} request The request object
39736      * @return {String} The url
39737      */
39738     buildUrl: function(request) {
39739         var me = this,
39740             url = me.getUrl(request);
39741
39742
39743         if (me.noCache) {
39744             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
39745         }
39746
39747         return url;
39748     },
39749
39750     /**
39751      * Get the url for the request taking into account the order of priority,
39752      * - The request
39753      * - The api
39754      * - The url
39755      * @private
39756      * @param {Ext.data.Request} request The request
39757      * @return {String} The url
39758      */
39759     getUrl: function(request){
39760         return request.url || this.api[request.action] || this.url;
39761     },
39762
39763     /**
39764      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
39765      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
39766      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
39767      * each of the methods that delegate to it.
39768      *
39769      * @param {Ext.data.Operation} operation The Ext.data.Operation object
39770      * @param {Function} callback The callback function to call when the Operation has completed
39771      * @param {Object} scope The scope in which to execute the callback
39772      */
39773     doRequest: function(operation, callback, scope) {
39774     },
39775
39776     /**
39777      * Optional callback function which can be used to clean up after a request has been completed.
39778      * @param {Ext.data.Request} request The Request object
39779      * @param {Boolean} success True if the request was successful
39780      * @method
39781      */
39782     afterRequest: Ext.emptyFn,
39783
39784     onDestroy: function() {
39785         Ext.destroy(this.reader, this.writer);
39786     }
39787 });
39788
39789 /**
39790  * @author Ed Spencer
39791  *
39792  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
39793  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
39794  * 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
39795  * Model}:
39796  *
39797  *     Ext.define('User', {
39798  *         extend: 'Ext.data.Model',
39799  *         fields: ['id', 'name', 'email']
39800  *     });
39801  *
39802  *     //The Store contains the AjaxProxy as an inline configuration
39803  *     var store = Ext.create('Ext.data.Store', {
39804  *         model: 'User',
39805  *         proxy: {
39806  *             type: 'ajax',
39807  *             url : 'users.json'
39808  *         }
39809  *     });
39810  *
39811  *     store.load();
39812  *
39813  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
39814  * the fields that we expect the server to return. Next we set up the Store itself, along with a
39815  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
39816  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
39817  * It's as if we'd done this:
39818  *
39819  *     new Ext.data.proxy.Ajax({
39820  *         url: 'users.json',
39821  *         model: 'User',
39822  *         reader: 'json'
39823  *     });
39824  *
39825  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
39826  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
39827  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
39828  *
39829  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
39830  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
39831  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
39832  * will be sent as a POST request).
39833  *
39834  * # Limitations
39835  *
39836  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
39837  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
39838  * talking to each other via AJAX.
39839  *
39840  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
39841  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
39842  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
39843  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
39844  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
39845  *
39846  * # Readers and Writers
39847  *
39848  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
39849  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
39850  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
39851  * Ext.data.reader.Reader Reader} instance:
39852  *
39853  *     var proxy = new Ext.data.proxy.Ajax({
39854  *         model: 'User',
39855  *         reader: {
39856  *             type: 'xml',
39857  *             root: 'users'
39858  *         }
39859  *     });
39860  *
39861  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
39862  *
39863  * # Url generation
39864  *
39865  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
39866  * each request. These are controlled with the following configuration options:
39867  *
39868  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
39869  * - {@link #sortParam} - controls how sort information is sent to the server
39870  * - {@link #groupParam} - controls how grouping information is sent to the server
39871  * - {@link #filterParam} - controls how filter information is sent to the server
39872  *
39873  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
39874  * the generated urls, let's say we're loading the Proxy with the following Operation:
39875  *
39876  *     var operation = new Ext.data.Operation({
39877  *         action: 'read',
39878  *         page  : 2
39879  *     });
39880  *
39881  * Now we'll issue the request for this Operation by calling {@link #read}:
39882  *
39883  *     var proxy = new Ext.data.proxy.Ajax({
39884  *         url: '/users'
39885  *     });
39886  *
39887  *     proxy.read(operation); //GET /users?page=2
39888  *
39889  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
39890  * to the server:
39891  *
39892  *     var proxy = new Ext.data.proxy.Ajax({
39893  *         url: '/users',
39894  *         pagePage: 'pageNumber'
39895  *     });
39896  *
39897  *     proxy.read(operation); //GET /users?pageNumber=2
39898  *
39899  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
39900  *
39901  *     var operation = new Ext.data.Operation({
39902  *         action: 'read',
39903  *         start : 50,
39904  *         limit : 25
39905  *     });
39906  *
39907  *     var proxy = new Ext.data.proxy.Ajax({
39908  *         url: '/users'
39909  *     });
39910  *
39911  *     proxy.read(operation); //GET /users?start=50&limit;=25
39912  *
39913  * Again we can customize this url:
39914  *
39915  *     var proxy = new Ext.data.proxy.Ajax({
39916  *         url: '/users',
39917  *         startParam: 'startIndex',
39918  *         limitParam: 'limitIndex'
39919  *     });
39920  *
39921  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
39922  *
39923  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
39924  * expressive Operation object:
39925  *
39926  *     var operation = new Ext.data.Operation({
39927  *         action: 'read',
39928  *         sorters: [
39929  *             new Ext.util.Sorter({
39930  *                 property : 'name',
39931  *                 direction: 'ASC'
39932  *             }),
39933  *             new Ext.util.Sorter({
39934  *                 property : 'age',
39935  *                 direction: 'DESC'
39936  *             })
39937  *         ],
39938  *         filters: [
39939  *             new Ext.util.Filter({
39940  *                 property: 'eyeColor',
39941  *                 value   : 'brown'
39942  *             })
39943  *         ]
39944  *     });
39945  *
39946  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
39947  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
39948  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
39949  *
39950  *     var proxy = new Ext.data.proxy.Ajax({
39951  *         url: '/users'
39952  *     });
39953  *
39954  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
39955  *
39956  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
39957  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
39958  * that format like this:
39959  *
39960  *      var proxy = new Ext.data.proxy.Ajax({
39961  *          url: '/users',
39962  *          sortParam: 'sortBy',
39963  *          filterParam: 'filterBy',
39964  *
39965  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
39966  *          encodeSorters: function(sorters) {
39967  *              var length   = sorters.length,
39968  *                  sortStrs = [],
39969  *                  sorter, i;
39970  *
39971  *              for (i = 0; i < length; i++) {
39972  *                  sorter = sorters[i];
39973  *
39974  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
39975  *              }
39976  *
39977  *              return sortStrs.join(",");
39978  *          }
39979  *      });
39980  *
39981  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
39982  *
39983  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
39984  *
39985  * @constructor
39986  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
39987  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
39988  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
39989  *
39990  * @param {Object} config (optional) Config object.
39991  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
39992  */
39993 Ext.define('Ext.data.proxy.Ajax', {
39994     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
39995     extend: 'Ext.data.proxy.Server',
39996     alias: 'proxy.ajax',
39997     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
39998     
39999     /**
40000      * @property {Object} actionMethods
40001      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
40002      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
40003      * correct RESTful methods.
40004      */
40005     actionMethods: {
40006         create : 'POST',
40007         read   : 'GET',
40008         update : 'POST',
40009         destroy: 'POST'
40010     },
40011     
40012     /**
40013      * @cfg {Object} headers
40014      * Any headers to add to the Ajax request. Defaults to undefined.
40015      */
40016     
40017     /**
40018      * @ignore
40019      */
40020     doRequest: function(operation, callback, scope) {
40021         var writer  = this.getWriter(),
40022             request = this.buildRequest(operation, callback, scope);
40023             
40024         if (operation.allowWrite()) {
40025             request = writer.write(request);
40026         }
40027         
40028         Ext.apply(request, {
40029             headers       : this.headers,
40030             timeout       : this.timeout,
40031             scope         : this,
40032             callback      : this.createRequestCallback(request, operation, callback, scope),
40033             method        : this.getMethod(request),
40034             disableCaching: false // explicitly set it to false, ServerProxy handles caching
40035         });
40036         
40037         Ext.Ajax.request(request);
40038         
40039         return request;
40040     },
40041     
40042     /**
40043      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
40044      * {@link #actionMethods}.
40045      * @param {Ext.data.Request} request The request object
40046      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
40047      */
40048     getMethod: function(request) {
40049         return this.actionMethods[request.action];
40050     },
40051     
40052     /**
40053      * @private
40054      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
40055      * of code duplication inside the returned function so we need to find a way to DRY this up.
40056      * @param {Ext.data.Request} request The Request object
40057      * @param {Ext.data.Operation} operation The Operation being executed
40058      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
40059      * passed to doRequest
40060      * @param {Object} scope The scope in which to execute the callback function
40061      * @return {Function} The callback function
40062      */
40063     createRequestCallback: function(request, operation, callback, scope) {
40064         var me = this;
40065         
40066         return function(options, success, response) {
40067             me.processResponse(success, operation, request, response, callback, scope);
40068         };
40069     }
40070 }, function() {
40071     //backwards compatibility, remove in Ext JS 5.0
40072     Ext.data.HttpProxy = this;
40073 });
40074
40075 /**
40076  * @author Ed Spencer
40077  *
40078  * A Model represents some object that your application manages. For example, one might define a Model for Users,
40079  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
40080  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
40081  * of the data-bound components in Ext.
40082  *
40083  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
40084  *
40085  *     Ext.define('User', {
40086  *         extend: 'Ext.data.Model',
40087  *         fields: [
40088  *             {name: 'name',  type: 'string'},
40089  *             {name: 'age',   type: 'int'},
40090  *             {name: 'phone', type: 'string'},
40091  *             {name: 'alive', type: 'boolean', defaultValue: true}
40092  *         ],
40093  *
40094  *         changeName: function() {
40095  *             var oldName = this.get('name'),
40096  *                 newName = oldName + " The Barbarian";
40097  *
40098  *             this.set('name', newName);
40099  *         }
40100  *     });
40101  *
40102  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
40103  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
40104  *
40105  * Now we can create instances of our User model and call any model logic we defined:
40106  *
40107  *     var user = Ext.create('User', {
40108  *         name : 'Conan',
40109  *         age  : 24,
40110  *         phone: '555-555-5555'
40111  *     });
40112  *
40113  *     user.changeName();
40114  *     user.get('name'); //returns "Conan The Barbarian"
40115  *
40116  * # Validations
40117  *
40118  * Models have built-in support for validations, which are executed against the validator functions in {@link
40119  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
40120  * models:
40121  *
40122  *     Ext.define('User', {
40123  *         extend: 'Ext.data.Model',
40124  *         fields: [
40125  *             {name: 'name',     type: 'string'},
40126  *             {name: 'age',      type: 'int'},
40127  *             {name: 'phone',    type: 'string'},
40128  *             {name: 'gender',   type: 'string'},
40129  *             {name: 'username', type: 'string'},
40130  *             {name: 'alive',    type: 'boolean', defaultValue: true}
40131  *         ],
40132  *
40133  *         validations: [
40134  *             {type: 'presence',  field: 'age'},
40135  *             {type: 'length',    field: 'name',     min: 2},
40136  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
40137  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
40138  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
40139  *         ]
40140  *     });
40141  *
40142  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
40143  * object:
40144  *
40145  *     var instance = Ext.create('User', {
40146  *         name: 'Ed',
40147  *         gender: 'Male',
40148  *         username: 'edspencer'
40149  *     });
40150  *
40151  *     var errors = instance.validate();
40152  *
40153  * # Associations
40154  *
40155  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
40156  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
40157  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
40158  * this:
40159  *
40160  *     Ext.define('Post', {
40161  *         extend: 'Ext.data.Model',
40162  *         fields: ['id', 'user_id'],
40163  *
40164  *         belongsTo: 'User',
40165  *         hasMany  : {model: 'Comment', name: 'comments'}
40166  *     });
40167  *
40168  *     Ext.define('Comment', {
40169  *         extend: 'Ext.data.Model',
40170  *         fields: ['id', 'user_id', 'post_id'],
40171  *
40172  *         belongsTo: 'Post'
40173  *     });
40174  *
40175  *     Ext.define('User', {
40176  *         extend: 'Ext.data.Model',
40177  *         fields: ['id'],
40178  *
40179  *         hasMany: [
40180  *             'Post',
40181  *             {model: 'Comment', name: 'comments'}
40182  *         ]
40183  *     });
40184  *
40185  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
40186  * usage and configuration of associations. Note that associations can also be specified like this:
40187  *
40188  *     Ext.define('User', {
40189  *         extend: 'Ext.data.Model',
40190  *         fields: ['id'],
40191  *
40192  *         associations: [
40193  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
40194  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
40195  *         ]
40196  *     });
40197  *
40198  * # Using a Proxy
40199  *
40200  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
40201  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
40202  * can be set directly on the Model:
40203  *
40204  *     Ext.define('User', {
40205  *         extend: 'Ext.data.Model',
40206  *         fields: ['id', 'name', 'email'],
40207  *
40208  *         proxy: {
40209  *             type: 'rest',
40210  *             url : '/users'
40211  *         }
40212  *     });
40213  *
40214  * 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
40215  * RESTful backend. Let's see how this works:
40216  *
40217  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
40218  *
40219  *     user.save(); //POST /users
40220  *
40221  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
40222  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
40223  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
40224  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
40225  *
40226  * Loading data via the Proxy is equally easy:
40227  *
40228  *     //get a reference to the User model class
40229  *     var User = Ext.ModelManager.getModel('User');
40230  *
40231  *     //Uses the configured RestProxy to make a GET request to /users/123
40232  *     User.load(123, {
40233  *         success: function(user) {
40234  *             console.log(user.getId()); //logs 123
40235  *         }
40236  *     });
40237  *
40238  * Models can also be updated and destroyed easily:
40239  *
40240  *     //the user Model we loaded in the last snippet:
40241  *     user.set('name', 'Edward Spencer');
40242  *
40243  *     //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
40244  *     user.save({
40245  *         success: function() {
40246  *             console.log('The User was updated');
40247  *         }
40248  *     });
40249  *
40250  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
40251  *     user.destroy({
40252  *         success: function() {
40253  *             console.log('The User was destroyed!');
40254  *         }
40255  *     });
40256  *
40257  * # Usage in Stores
40258  *
40259  * 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
40260  * creating a {@link Ext.data.Store Store}:
40261  *
40262  *     var store = Ext.create('Ext.data.Store', {
40263  *         model: 'User'
40264  *     });
40265  *
40266  *     //uses the Proxy we set up on Model to load the Store data
40267  *     store.load();
40268  *
40269  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
40270  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
40271  * Ext.data.Store Store docs} for more information on Stores.
40272  *
40273  * @constructor
40274  * Creates new Model instance.
40275  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
40276  * @param {Number} id (optional) Unique ID to assign to this model instance
40277  */
40278 Ext.define('Ext.data.Model', {
40279     alternateClassName: 'Ext.data.Record',
40280
40281     mixins: {
40282         observable: 'Ext.util.Observable'
40283     },
40284
40285     requires: [
40286         'Ext.ModelManager',
40287         'Ext.data.IdGenerator',
40288         'Ext.data.Field',
40289         'Ext.data.Errors',
40290         'Ext.data.Operation',
40291         'Ext.data.validations',
40292         'Ext.data.proxy.Ajax',
40293         'Ext.util.MixedCollection'
40294     ],
40295
40296     onClassExtended: function(cls, data) {
40297         var onBeforeClassCreated = data.onBeforeClassCreated;
40298
40299         data.onBeforeClassCreated = function(cls, data) {
40300             var me = this,
40301                 name = Ext.getClassName(cls),
40302                 prototype = cls.prototype,
40303                 superCls = cls.prototype.superclass,
40304
40305                 validations = data.validations || [],
40306                 fields = data.fields || [],
40307                 associations = data.associations || [],
40308                 belongsTo = data.belongsTo,
40309                 hasMany = data.hasMany,
40310                 idgen = data.idgen,
40311
40312                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
40313                     return field.name;
40314                 }),
40315
40316                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
40317                     return association.name;
40318                 }),
40319
40320                 superValidations = superCls.validations,
40321                 superFields = superCls.fields,
40322                 superAssociations = superCls.associations,
40323
40324                 association, i, ln,
40325                 dependencies = [];
40326
40327             // Save modelName on class and its prototype
40328             cls.modelName = name;
40329             prototype.modelName = name;
40330
40331             // Merge the validations of the superclass and the new subclass
40332             if (superValidations) {
40333                 validations = superValidations.concat(validations);
40334             }
40335
40336             data.validations = validations;
40337
40338             // Merge the fields of the superclass and the new subclass
40339             if (superFields) {
40340                 fields = superFields.items.concat(fields);
40341             }
40342
40343             for (i = 0, ln = fields.length; i < ln; ++i) {
40344                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
40345             }
40346
40347             data.fields = fieldsMixedCollection;
40348
40349             if (idgen) {
40350                 data.idgen = Ext.data.IdGenerator.get(idgen);
40351             }
40352
40353             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
40354             //we support that here
40355             if (belongsTo) {
40356                 belongsTo = Ext.Array.from(belongsTo);
40357
40358                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
40359                     association = belongsTo[i];
40360
40361                     if (!Ext.isObject(association)) {
40362                         association = {model: association};
40363                     }
40364
40365                     association.type = 'belongsTo';
40366                     associations.push(association);
40367                 }
40368
40369                 delete data.belongsTo;
40370             }
40371
40372             if (hasMany) {
40373                 hasMany = Ext.Array.from(hasMany);
40374                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
40375                     association = hasMany[i];
40376
40377                     if (!Ext.isObject(association)) {
40378                         association = {model: association};
40379                     }
40380
40381                     association.type = 'hasMany';
40382                     associations.push(association);
40383                 }
40384
40385                 delete data.hasMany;
40386             }
40387
40388             if (superAssociations) {
40389                 associations = superAssociations.items.concat(associations);
40390             }
40391
40392             for (i = 0, ln = associations.length; i < ln; ++i) {
40393                 dependencies.push('association.' + associations[i].type.toLowerCase());
40394             }
40395
40396             if (data.proxy) {
40397                 if (typeof data.proxy === 'string') {
40398                     dependencies.push('proxy.' + data.proxy);
40399                 }
40400                 else if (typeof data.proxy.type === 'string') {
40401                     dependencies.push('proxy.' + data.proxy.type);
40402                 }
40403             }
40404
40405             Ext.require(dependencies, function() {
40406                 Ext.ModelManager.registerType(name, cls);
40407
40408                 for (i = 0, ln = associations.length; i < ln; ++i) {
40409                     association = associations[i];
40410
40411                     Ext.apply(association, {
40412                         ownerModel: name,
40413                         associatedModel: association.model
40414                     });
40415
40416                     if (Ext.ModelManager.getModel(association.model) === undefined) {
40417                         Ext.ModelManager.registerDeferredAssociation(association);
40418                     } else {
40419                         associationsMixedCollection.add(Ext.data.Association.create(association));
40420                     }
40421                 }
40422
40423                 data.associations = associationsMixedCollection;
40424
40425                 onBeforeClassCreated.call(me, cls, data);
40426
40427                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
40428
40429                 // Fire the onModelDefined template method on ModelManager
40430                 Ext.ModelManager.onModelDefined(cls);
40431             });
40432         };
40433     },
40434
40435     inheritableStatics: {
40436         /**
40437          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
40438          * {@link Ext#createByAlias Ext.createByAlias}.
40439          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
40440          * @return {Ext.data.proxy.Proxy}
40441          * @static
40442          * @inheritable
40443          */
40444         setProxy: function(proxy) {
40445             //make sure we have an Ext.data.proxy.Proxy object
40446             if (!proxy.isProxy) {
40447                 if (typeof proxy == "string") {
40448                     proxy = {
40449                         type: proxy
40450                     };
40451                 }
40452                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
40453             }
40454             proxy.setModel(this);
40455             this.proxy = this.prototype.proxy = proxy;
40456
40457             return proxy;
40458         },
40459
40460         /**
40461          * Returns the configured Proxy for this Model
40462          * @return {Ext.data.proxy.Proxy} The proxy
40463          * @static
40464          * @inheritable
40465          */
40466         getProxy: function() {
40467             return this.proxy;
40468         },
40469
40470         /**
40471          * Asynchronously loads a model instance by id. Sample usage:
40472          *
40473          *     MyApp.User = Ext.define('User', {
40474          *         extend: 'Ext.data.Model',
40475          *         fields: [
40476          *             {name: 'id', type: 'int'},
40477          *             {name: 'name', type: 'string'}
40478          *         ]
40479          *     });
40480          *
40481          *     MyApp.User.load(10, {
40482          *         scope: this,
40483          *         failure: function(record, operation) {
40484          *             //do something if the load failed
40485          *         },
40486          *         success: function(record, operation) {
40487          *             //do something if the load succeeded
40488          *         },
40489          *         callback: function(record, operation) {
40490          *             //do something whether the load succeeded or failed
40491          *         }
40492          *     });
40493          *
40494          * @param {Number} id The id of the model to load
40495          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
40496          * optional scope
40497          * @static
40498          * @inheritable
40499          */
40500         load: function(id, config) {
40501             config = Ext.apply({}, config);
40502             config = Ext.applyIf(config, {
40503                 action: 'read',
40504                 id    : id
40505             });
40506
40507             var operation  = Ext.create('Ext.data.Operation', config),
40508                 scope      = config.scope || this,
40509                 record     = null,
40510                 callback;
40511
40512             callback = function(operation) {
40513                 if (operation.wasSuccessful()) {
40514                     record = operation.getRecords()[0];
40515                     Ext.callback(config.success, scope, [record, operation]);
40516                 } else {
40517                     Ext.callback(config.failure, scope, [record, operation]);
40518                 }
40519                 Ext.callback(config.callback, scope, [record, operation]);
40520             };
40521
40522             this.proxy.read(operation, callback, this);
40523         }
40524     },
40525
40526     statics: {
40527         PREFIX : 'ext-record',
40528         AUTO_ID: 1,
40529         EDIT   : 'edit',
40530         REJECT : 'reject',
40531         COMMIT : 'commit',
40532
40533         /**
40534          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
40535          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
40536          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
40537          *
40538          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
40539          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
40540          *
40541          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
40542          * @return {String} auto-generated string id, `"ext-record-i++"`;
40543          * @static
40544          */
40545         id: function(rec) {
40546             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
40547             rec.phantom = true;
40548             rec.internalId = id;
40549             return id;
40550         }
40551     },
40552
40553     /**
40554      * @cfg {String/Object} idgen
40555      * The id generator to use for this model. The default id generator does not generate
40556      * values for the {@link #idProperty}.
40557      *
40558      * This can be overridden at the model level to provide a custom generator for a model.
40559      * The simplest form of this would be:
40560      *
40561      *      Ext.define('MyApp.data.MyModel', {
40562      *          extend: 'Ext.data.Model',
40563      *          requires: ['Ext.data.SequentialIdGenerator'],
40564      *          idgen: 'sequential',
40565      *          ...
40566      *      });
40567      *
40568      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
40569      * as 1, 2, 3 etc..
40570      *
40571      * Another useful id generator is {@link Ext.data.UuidGenerator}:
40572      *
40573      *      Ext.define('MyApp.data.MyModel', {
40574      *          extend: 'Ext.data.Model',
40575      *          requires: ['Ext.data.UuidGenerator'],
40576      *          idgen: 'uuid',
40577      *          ...
40578      *      });
40579      *
40580      * An id generation can also be further configured:
40581      *
40582      *      Ext.define('MyApp.data.MyModel', {
40583      *          extend: 'Ext.data.Model',
40584      *          idgen: {
40585      *              type: 'sequential',
40586      *              seed: 1000,
40587      *              prefix: 'ID_'
40588      *          }
40589      *      });
40590      *
40591      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
40592      *
40593      * If multiple models share an id space, a single generator can be shared:
40594      *
40595      *      Ext.define('MyApp.data.MyModelX', {
40596      *          extend: 'Ext.data.Model',
40597      *          idgen: {
40598      *              type: 'sequential',
40599      *              id: 'xy'
40600      *          }
40601      *      });
40602      *
40603      *      Ext.define('MyApp.data.MyModelY', {
40604      *          extend: 'Ext.data.Model',
40605      *          idgen: {
40606      *              type: 'sequential',
40607      *              id: 'xy'
40608      *          }
40609      *      });
40610      *
40611      * For more complex, shared id generators, a custom generator is the best approach.
40612      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
40613      *
40614      * @markdown
40615      */
40616     idgen: {
40617         isGenerator: true,
40618         type: 'default',
40619
40620         generate: function () {
40621             return null;
40622         },
40623         getRecId: function (rec) {
40624             return rec.modelName + '-' + rec.internalId;
40625         }
40626     },
40627
40628     /**
40629      * @property {Boolean} editing
40630      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
40631      */
40632     editing : false,
40633
40634     /**
40635      * @property {Boolean} dirty
40636      * True if this Record has been modified. Read-only.
40637      */
40638     dirty : false,
40639
40640     /**
40641      * @cfg {String} persistenceProperty
40642      * The property on this Persistable object that its data is saved to. Defaults to 'data'
40643      * (e.g. all persistable data resides in this.data.)
40644      */
40645     persistenceProperty: 'data',
40646
40647     evented: false,
40648     isModel: true,
40649
40650     /**
40651      * @property {Boolean} phantom
40652      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
40653      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
40654      */
40655     phantom : false,
40656
40657     /**
40658      * @cfg {String} idProperty
40659      * The name of the field treated as this Model's unique id. Defaults to 'id'.
40660      */
40661     idProperty: 'id',
40662
40663     /**
40664      * @cfg {String} defaultProxyType
40665      * The string type of the default Model Proxy. Defaults to 'ajax'.
40666      */
40667     defaultProxyType: 'ajax',
40668
40669     // Fields config and property
40670     /**
40671      * @cfg {Object[]/String[]} fields
40672      * The fields for this model.
40673      */
40674     /**
40675      * @property {Ext.util.MixedCollection} fields
40676      * The fields defined on this model.
40677      */
40678
40679     /**
40680      * @cfg {Object[]} validations
40681      * An array of {@link Ext.data.validations validations} for this model.
40682      */
40683
40684     // Associations configs and properties
40685     /**
40686      * @cfg {Object[]} associations
40687      * An array of {@link Ext.data.Association associations} for this model.
40688      */
40689     /**
40690      * @cfg {String/Object/String[]/Object[]} hasMany
40691      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
40692      */
40693     /**
40694      * @cfg {String/Object/String[]/Object[]} belongsTo
40695      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
40696      */
40697     /**
40698      * @property {Ext.util.MixedCollection} associations
40699      * {@link Ext.data.Association Associations} defined on this model.
40700      */
40701
40702     /**
40703      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
40704      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
40705      */
40706
40707     // raw not documented intentionally, meant to be used internally.
40708     constructor: function(data, id, raw) {
40709         data = data || {};
40710
40711         var me = this,
40712             fields,
40713             length,
40714             field,
40715             name,
40716             i,
40717             newId,
40718             isArray = Ext.isArray(data),
40719             newData = isArray ? {} : null; // to hold mapped array data if needed
40720
40721         /**
40722          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
40723          * @property internalId
40724          * @type String
40725          * @private
40726          */
40727         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
40728
40729         /**
40730          * @property {Object} raw The raw data used to create this model if created via a reader.
40731          */
40732         me.raw = raw;
40733
40734         Ext.applyIf(me, {
40735             data: {}
40736         });
40737
40738         /**
40739          * @property {Object} modified Key: value pairs of all fields whose values have changed
40740          */
40741         me.modified = {};
40742
40743         // Deal with spelling error in previous releases
40744         if (me.persistanceProperty) {
40745             me.persistenceProperty = me.persistanceProperty;
40746         }
40747         me[me.persistenceProperty] = {};
40748
40749         me.mixins.observable.constructor.call(me);
40750
40751         //add default field values if present
40752         fields = me.fields.items;
40753         length = fields.length;
40754
40755         for (i = 0; i < length; i++) {
40756             field = fields[i];
40757             name  = field.name;
40758
40759             if (isArray){
40760                 // Have to map array data so the values get assigned to the named fields
40761                 // rather than getting set as the field names with undefined values.
40762                 newData[name] = data[i];
40763             }
40764             else if (data[name] === undefined) {
40765                 data[name] = field.defaultValue;
40766             }
40767         }
40768
40769         me.set(newData || data);
40770
40771         if (me.getId()) {
40772             me.phantom = false;
40773         } else if (me.phantom) {
40774             newId = me.idgen.generate();
40775             if (newId !== null) {
40776                 me.setId(newId);
40777             }
40778         }
40779
40780         // clear any dirty/modified since we're initializing
40781         me.dirty = false;
40782         me.modified = {};
40783
40784         if (typeof me.init == 'function') {
40785             me.init();
40786         }
40787
40788         me.id = me.idgen.getRecId(me);
40789     },
40790
40791     /**
40792      * Returns the value of the given field
40793      * @param {String} fieldName The field to fetch the value for
40794      * @return {Object} The value
40795      */
40796     get: function(field) {
40797         return this[this.persistenceProperty][field];
40798     },
40799
40800     /**
40801      * Sets the given field to the given value, marks the instance as dirty
40802      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
40803      * @param {Object} value The value to set
40804      */
40805     set: function(fieldName, value) {
40806         var me = this,
40807             fields = me.fields,
40808             modified = me.modified,
40809             convertFields = [],
40810             field, key, i, currentValue, notEditing, count, length;
40811
40812         /*
40813          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
40814          * set those last so that all other possible data is set before the convert function is called
40815          */
40816         if (arguments.length == 1 && Ext.isObject(fieldName)) {
40817             notEditing = !me.editing;
40818             count = 0;
40819             for (key in fieldName) {
40820                 if (fieldName.hasOwnProperty(key)) {
40821
40822                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
40823                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
40824                     field = fields.get(key);
40825                     if (field && field.convert !== field.type.convert) {
40826                         convertFields.push(key);
40827                         continue;
40828                     }
40829
40830                     if (!count && notEditing) {
40831                         me.beginEdit();
40832                     }
40833                     ++count;
40834                     me.set(key, fieldName[key]);
40835                 }
40836             }
40837
40838             length = convertFields.length;
40839             if (length) {
40840                 if (!count && notEditing) {
40841                     me.beginEdit();
40842                 }
40843                 count += length;
40844                 for (i = 0; i < length; i++) {
40845                     field = convertFields[i];
40846                     me.set(field, fieldName[field]);
40847                 }
40848             }
40849
40850             if (notEditing && count) {
40851                 me.endEdit();
40852             }
40853         } else {
40854             if (fields) {
40855                 field = fields.get(fieldName);
40856
40857                 if (field && field.convert) {
40858                     value = field.convert(value, me);
40859                 }
40860             }
40861             currentValue = me.get(fieldName);
40862             me[me.persistenceProperty][fieldName] = value;
40863
40864             if (field && field.persist && !me.isEqual(currentValue, value)) {
40865                 if (me.isModified(fieldName)) {
40866                     if (me.isEqual(modified[fieldName], value)) {
40867                         // the original value in me.modified equals the new value, so the
40868                         // field is no longer modified
40869                         delete modified[fieldName];
40870                         // we might have removed the last modified field, so check to see if
40871                         // there are any modified fields remaining and correct me.dirty:
40872                         me.dirty = false;
40873                         for (key in modified) {
40874                             if (modified.hasOwnProperty(key)){
40875                                 me.dirty = true;
40876                                 break;
40877                             }
40878                         }
40879                     }
40880                 } else {
40881                     me.dirty = true;
40882                     modified[fieldName] = currentValue;
40883                 }
40884             }
40885
40886             if (!me.editing) {
40887                 me.afterEdit();
40888             }
40889         }
40890     },
40891
40892     /**
40893      * Checks if two values are equal, taking into account certain
40894      * special factors, for example dates.
40895      * @private
40896      * @param {Object} a The first value
40897      * @param {Object} b The second value
40898      * @return {Boolean} True if the values are equal
40899      */
40900     isEqual: function(a, b){
40901         if (Ext.isDate(a) && Ext.isDate(b)) {
40902             return a.getTime() === b.getTime();
40903         }
40904         return a === b;
40905     },
40906
40907     /**
40908      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
40909      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
40910      */
40911     beginEdit : function(){
40912         var me = this;
40913         if (!me.editing) {
40914             me.editing = true;
40915             me.dirtySave = me.dirty;
40916             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
40917             me.modifiedSave = Ext.apply({}, me.modified);
40918         }
40919     },
40920
40921     /**
40922      * Cancels all changes made in the current edit operation.
40923      */
40924     cancelEdit : function(){
40925         var me = this;
40926         if (me.editing) {
40927             me.editing = false;
40928             // reset the modified state, nothing changed since the edit began
40929             me.modified = me.modifiedSave;
40930             me[me.persistenceProperty] = me.dataSave;
40931             me.dirty = me.dirtySave;
40932             delete me.modifiedSave;
40933             delete me.dataSave;
40934             delete me.dirtySave;
40935         }
40936     },
40937
40938     /**
40939      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
40940      * fire).
40941      * @param {Boolean} silent True to not notify the store of the change
40942      */
40943     endEdit : function(silent){
40944         var me = this,
40945             didChange;
40946             
40947         if (me.editing) {
40948             me.editing = false;
40949             didChange = me.dirty || me.changedWhileEditing();
40950             delete me.modifiedSave;
40951             delete me.dataSave;
40952             delete me.dirtySave;
40953             if (silent !== true && didChange) {
40954                 me.afterEdit();
40955             }
40956         }
40957     },
40958     
40959     /**
40960      * Checks if the underlying data has changed during an edit. This doesn't necessarily
40961      * mean the record is dirty, however we still need to notify the store since it may need
40962      * to update any views.
40963      * @private
40964      * @return {Boolean} True if the underlying data has changed during an edit.
40965      */
40966     changedWhileEditing: function(){
40967         var me = this,
40968             saved = me.dataSave,
40969             data = me[me.persistenceProperty],
40970             key;
40971             
40972         for (key in data) {
40973             if (data.hasOwnProperty(key)) {
40974                 if (!me.isEqual(data[key], saved[key])) {
40975                     return true;
40976                 }
40977             }
40978         }
40979         return false; 
40980     },
40981
40982     /**
40983      * Gets a hash of only the fields that have been modified since this Model was created or commited.
40984      * @return {Object}
40985      */
40986     getChanges : function(){
40987         var modified = this.modified,
40988             changes  = {},
40989             field;
40990
40991         for (field in modified) {
40992             if (modified.hasOwnProperty(field)){
40993                 changes[field] = this.get(field);
40994             }
40995         }
40996
40997         return changes;
40998     },
40999
41000     /**
41001      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
41002      * @param {String} fieldName {@link Ext.data.Field#name}
41003      * @return {Boolean}
41004      */
41005     isModified : function(fieldName) {
41006         return this.modified.hasOwnProperty(fieldName);
41007     },
41008
41009     /**
41010      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
41011      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
41012      *
41013      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
41014      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
41015      */
41016     setDirty : function() {
41017         var me = this,
41018             name;
41019
41020         me.dirty = true;
41021
41022         me.fields.each(function(field) {
41023             if (field.persist) {
41024                 name = field.name;
41025                 me.modified[name] = me.get(name);
41026             }
41027         }, me);
41028     },
41029
41030
41031     /**
41032      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
41033      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
41034      * reverted to their original values.
41035      *
41036      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
41037      * operations.
41038      *
41039      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41040      * Defaults to false.
41041      */
41042     reject : function(silent) {
41043         var me = this,
41044             modified = me.modified,
41045             field;
41046
41047         for (field in modified) {
41048             if (modified.hasOwnProperty(field)) {
41049                 if (typeof modified[field] != "function") {
41050                     me[me.persistenceProperty][field] = modified[field];
41051                 }
41052             }
41053         }
41054
41055         me.dirty = false;
41056         me.editing = false;
41057         me.modified = {};
41058
41059         if (silent !== true) {
41060             me.afterReject();
41061         }
41062     },
41063
41064     /**
41065      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
41066      * instance since either creation or the last commit operation.
41067      *
41068      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
41069      * operations.
41070      *
41071      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41072      * Defaults to false.
41073      */
41074     commit : function(silent) {
41075         var me = this;
41076
41077         me.phantom = me.dirty = me.editing = false;
41078         me.modified = {};
41079
41080         if (silent !== true) {
41081             me.afterCommit();
41082         }
41083     },
41084
41085     /**
41086      * Creates a copy (clone) of this Model instance.
41087      *
41088      * @param {String} [id] A new id, defaults to the id of the instance being copied.
41089      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
41090      *
41091      *     var rec = record.copy(); // clone the record
41092      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
41093      *
41094      * @return {Ext.data.Model}
41095      */
41096     copy : function(newId) {
41097         var me = this;
41098
41099         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
41100     },
41101
41102     /**
41103      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41104      * {@link Ext#createByAlias Ext.createByAlias}.
41105      *
41106      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41107      * @return {Ext.data.proxy.Proxy}
41108      */
41109     setProxy: function(proxy) {
41110         //make sure we have an Ext.data.proxy.Proxy object
41111         if (!proxy.isProxy) {
41112             if (typeof proxy === "string") {
41113                 proxy = {
41114                     type: proxy
41115                 };
41116             }
41117             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41118         }
41119         proxy.setModel(this.self);
41120         this.proxy = proxy;
41121
41122         return proxy;
41123     },
41124
41125     /**
41126      * Returns the configured Proxy for this Model.
41127      * @return {Ext.data.proxy.Proxy} The proxy
41128      */
41129     getProxy: function() {
41130         return this.proxy;
41131     },
41132
41133     /**
41134      * Validates the current data against all of its configured {@link #validations}.
41135      * @return {Ext.data.Errors} The errors object
41136      */
41137     validate: function() {
41138         var errors      = Ext.create('Ext.data.Errors'),
41139             validations = this.validations,
41140             validators  = Ext.data.validations,
41141             length, validation, field, valid, type, i;
41142
41143         if (validations) {
41144             length = validations.length;
41145
41146             for (i = 0; i < length; i++) {
41147                 validation = validations[i];
41148                 field = validation.field || validation.name;
41149                 type  = validation.type;
41150                 valid = validators[type](validation, this.get(field));
41151
41152                 if (!valid) {
41153                     errors.add({
41154                         field  : field,
41155                         message: validation.message || validators[type + 'Message']
41156                     });
41157                 }
41158             }
41159         }
41160
41161         return errors;
41162     },
41163
41164     /**
41165      * Checks if the model is valid. See {@link #validate}.
41166      * @return {Boolean} True if the model is valid.
41167      */
41168     isValid: function(){
41169         return this.validate().isValid();
41170     },
41171
41172     /**
41173      * Saves the model instance using the configured proxy.
41174      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41175      * @return {Ext.data.Model} The Model instance
41176      */
41177     save: function(options) {
41178         options = Ext.apply({}, options);
41179
41180         var me     = this,
41181             action = me.phantom ? 'create' : 'update',
41182             record = null,
41183             scope  = options.scope || me,
41184             operation,
41185             callback;
41186
41187         Ext.apply(options, {
41188             records: [me],
41189             action : action
41190         });
41191
41192         operation = Ext.create('Ext.data.Operation', options);
41193
41194         callback = function(operation) {
41195             if (operation.wasSuccessful()) {
41196                 record = operation.getRecords()[0];
41197                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
41198                 //ModelCache is in place
41199                 me.set(record.data);
41200                 record.dirty = false;
41201
41202                 Ext.callback(options.success, scope, [record, operation]);
41203             } else {
41204                 Ext.callback(options.failure, scope, [record, operation]);
41205             }
41206
41207             Ext.callback(options.callback, scope, [record, operation]);
41208         };
41209
41210         me.getProxy()[action](operation, callback, me);
41211
41212         return me;
41213     },
41214
41215     /**
41216      * Destroys the model using the configured proxy.
41217      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41218      * @return {Ext.data.Model} The Model instance
41219      */
41220     destroy: function(options){
41221         options = Ext.apply({}, options);
41222
41223         var me     = this,
41224             record = null,
41225             scope  = options.scope || me,
41226             operation,
41227             callback;
41228
41229         Ext.apply(options, {
41230             records: [me],
41231             action : 'destroy'
41232         });
41233
41234         operation = Ext.create('Ext.data.Operation', options);
41235         callback = function(operation) {
41236             if (operation.wasSuccessful()) {
41237                 Ext.callback(options.success, scope, [record, operation]);
41238             } else {
41239                 Ext.callback(options.failure, scope, [record, operation]);
41240             }
41241             Ext.callback(options.callback, scope, [record, operation]);
41242         };
41243
41244         me.getProxy().destroy(operation, callback, me);
41245         return me;
41246     },
41247
41248     /**
41249      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
41250      * @return {Number} The id
41251      */
41252     getId: function() {
41253         return this.get(this.idProperty);
41254     },
41255
41256     /**
41257      * Sets the model instance's id field to the given id.
41258      * @param {Number} id The new id
41259      */
41260     setId: function(id) {
41261         this.set(this.idProperty, id);
41262     },
41263
41264     /**
41265      * Tells this model instance that it has been added to a store.
41266      * @param {Ext.data.Store} store The store to which this model has been added.
41267      */
41268     join : function(store) {
41269         /**
41270          * @property {Ext.data.Store} store
41271          * The {@link Ext.data.Store Store} to which this Record belongs.
41272          */
41273         this.store = store;
41274     },
41275
41276     /**
41277      * Tells this model instance that it has been removed from the store.
41278      * @param {Ext.data.Store} store The store from which this model has been removed.
41279      */
41280     unjoin: function(store) {
41281         delete this.store;
41282     },
41283
41284     /**
41285      * @private
41286      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41287      * afterEdit method is called
41288      */
41289     afterEdit : function() {
41290         this.callStore('afterEdit');
41291     },
41292
41293     /**
41294      * @private
41295      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41296      * afterReject method is called
41297      */
41298     afterReject : function() {
41299         this.callStore("afterReject");
41300     },
41301
41302     /**
41303      * @private
41304      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41305      * afterCommit method is called
41306      */
41307     afterCommit: function() {
41308         this.callStore('afterCommit');
41309     },
41310
41311     /**
41312      * @private
41313      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
41314      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
41315      * will always be called with the model instance as its single argument.
41316      * @param {String} fn The function to call on the store
41317      */
41318     callStore: function(fn) {
41319         var store = this.store;
41320
41321         if (store !== undefined && typeof store[fn] == "function") {
41322             store[fn](this);
41323         }
41324     },
41325
41326     /**
41327      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
41328      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
41329      *
41330      *     {
41331      *         orders: [
41332      *             {
41333      *                 id: 123,
41334      *                 status: 'shipped',
41335      *                 orderItems: [
41336      *                     ...
41337      *                 ]
41338      *             }
41339      *         ]
41340      *     }
41341      *
41342      * @return {Object} The nested data set for the Model's loaded associations
41343      */
41344     getAssociatedData: function(){
41345         return this.prepareAssociatedData(this, [], null);
41346     },
41347
41348     /**
41349      * @private
41350      * This complex-looking method takes a given Model instance and returns an object containing all data from
41351      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
41352      * @param {Ext.data.Model} record The Model instance
41353      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
41354      * @param {String} associationType (optional) The name of the type of association to limit to.
41355      * @return {Object} The nested data set for the Model's loaded associations
41356      */
41357     prepareAssociatedData: function(record, ids, associationType) {
41358         //we keep track of all of the internalIds of the models that we have loaded so far in here
41359         var associations     = record.associations.items,
41360             associationCount = associations.length,
41361             associationData  = {},
41362             associatedStore, associatedName, associatedRecords, associatedRecord,
41363             associatedRecordCount, association, id, i, j, type, allow;
41364
41365         for (i = 0; i < associationCount; i++) {
41366             association = associations[i];
41367             type = association.type;
41368             allow = true;
41369             if (associationType) {
41370                 allow = type == associationType;
41371             }
41372             if (allow && type == 'hasMany') {
41373
41374                 //this is the hasMany store filled with the associated data
41375                 associatedStore = record[association.storeName];
41376
41377                 //we will use this to contain each associated record's data
41378                 associationData[association.name] = [];
41379
41380                 //if it's loaded, put it into the association data
41381                 if (associatedStore && associatedStore.data.length > 0) {
41382                     associatedRecords = associatedStore.data.items;
41383                     associatedRecordCount = associatedRecords.length;
41384
41385                     //now we're finally iterating over the records in the association. We do this recursively
41386                     for (j = 0; j < associatedRecordCount; j++) {
41387                         associatedRecord = associatedRecords[j];
41388                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
41389                         id = associatedRecord.id;
41390
41391                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
41392                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
41393                         if (Ext.Array.indexOf(ids, id) == -1) {
41394                             ids.push(id);
41395
41396                             associationData[association.name][j] = associatedRecord.data;
41397                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
41398                         }
41399                     }
41400                 }
41401             } else if (allow && type == 'belongsTo') {
41402                 associatedRecord = record[association.instanceName];
41403                 if (associatedRecord !== undefined) {
41404                     id = associatedRecord.id;
41405                     if (Ext.Array.indexOf(ids, id) == -1) {
41406                         ids.push(id);
41407                         associationData[association.name] = associatedRecord.data;
41408                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
41409                     }
41410                 }
41411             }
41412         }
41413
41414         return associationData;
41415     }
41416 });
41417
41418 /**
41419  * @docauthor Evan Trimboli <evan@sencha.com>
41420  *
41421  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
41422  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
41423  * referred to via it's identifier:
41424  *
41425  *     Ext.create('Ext.data.Store', {
41426  *         model: 'SomeModel',
41427  *         storeId: 'myStore'
41428  *     });
41429  *
41430  *     var store = Ext.data.StoreManager.lookup('myStore');
41431  *
41432  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
41433  *
41434  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
41435  * it with any Component that consumes data from a store:
41436  *
41437  *     Ext.create('Ext.data.Store', {
41438  *         model: 'SomeModel',
41439  *         storeId: 'myStore'
41440  *     });
41441  *
41442  *     Ext.create('Ext.view.View', {
41443  *         store: 'myStore',
41444  *         // other configuration here
41445  *     });
41446  *
41447  */
41448 Ext.define('Ext.data.StoreManager', {
41449     extend: 'Ext.util.MixedCollection',
41450     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
41451     singleton: true,
41452     uses: ['Ext.data.ArrayStore'],
41453     
41454     /**
41455      * @cfg {Object} listeners @hide
41456      */
41457
41458     /**
41459      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
41460      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
41461      * @param {Ext.data.Store...} stores Any number of Store instances
41462      */
41463     register : function() {
41464         for (var i = 0, s; (s = arguments[i]); i++) {
41465             this.add(s);
41466         }
41467     },
41468
41469     /**
41470      * Unregisters one or more Stores with the StoreManager
41471      * @param {String/Object...} stores Any number of Store instances or ID-s
41472      */
41473     unregister : function() {
41474         for (var i = 0, s; (s = arguments[i]); i++) {
41475             this.remove(this.lookup(s));
41476         }
41477     },
41478
41479     /**
41480      * Gets a registered Store by id
41481      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
41482      * @return {Ext.data.Store}
41483      */
41484     lookup : function(store) {
41485         // handle the case when we are given an array or an array of arrays.
41486         if (Ext.isArray(store)) {
41487             var fields = ['field1'], 
41488                 expand = !Ext.isArray(store[0]),
41489                 data = store,
41490                 i,
41491                 len;
41492                 
41493             if(expand){
41494                 data = [];
41495                 for (i = 0, len = store.length; i < len; ++i) {
41496                     data.push([store[i]]);
41497                 }
41498             } else {
41499                 for(i = 2, len = store[0].length; i <= len; ++i){
41500                     fields.push('field' + i);
41501                 }
41502             }
41503             return Ext.create('Ext.data.ArrayStore', {
41504                 data  : data,
41505                 fields: fields,
41506                 autoDestroy: true,
41507                 autoCreated: true,
41508                 expanded: expand
41509             });
41510         }
41511         
41512         if (Ext.isString(store)) {
41513             // store id
41514             return this.get(store);
41515         } else {
41516             // store instance or store config
41517             return Ext.data.AbstractStore.create(store);
41518         }
41519     },
41520
41521     // getKey implementation for MixedCollection
41522     getKey : function(o) {
41523          return o.storeId;
41524     }
41525 }, function() {    
41526     /**
41527      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
41528      * Sample usage:
41529      *
41530      *     Ext.regStore('AllUsers', {
41531      *         model: 'User'
41532      *     });
41533      *
41534      *     // the store can now easily be used throughout the application
41535      *     new Ext.List({
41536      *         store: 'AllUsers',
41537      *         ... other config
41538      *     });
41539      *
41540      * @param {String} id The id to set on the new store
41541      * @param {Object} config The store config
41542      * @member Ext
41543      * @method regStore
41544      */
41545     Ext.regStore = function(name, config) {
41546         var store;
41547
41548         if (Ext.isObject(name)) {
41549             config = name;
41550         } else {
41551             config.storeId = name;
41552         }
41553
41554         if (config instanceof Ext.data.Store) {
41555             store = config;
41556         } else {
41557             store = Ext.create('Ext.data.Store', config);
41558         }
41559
41560         return Ext.data.StoreManager.register(store);
41561     };
41562
41563     /**
41564      * Shortcut to {@link Ext.data.StoreManager#lookup}.
41565      * @member Ext
41566      * @method getStore
41567      * @alias Ext.data.StoreManager#lookup
41568      */
41569     Ext.getStore = function(name) {
41570         return Ext.data.StoreManager.lookup(name);
41571     };
41572 });
41573
41574 /**
41575  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
41576  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
41577  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
41578  * at the time the Container is created, or they may be added dynamically via the
41579  * {@link Ext.container.Container#add add} method.
41580  *
41581  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
41582  *
41583  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
41584  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
41585  *
41586  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
41587  * subclass Component.
41588  *
41589  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
41590  * to create custom Components.
41591  *
41592  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
41593  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
41594  * Component hierarchy.
41595  *
41596  * This is the list of all valid xtypes:
41597  *
41598  *     xtype            Class
41599  *     -------------    ------------------
41600  *     button           {@link Ext.button.Button}
41601  *     buttongroup      {@link Ext.container.ButtonGroup}
41602  *     colorpalette     {@link Ext.picker.Color}
41603  *     component        {@link Ext.Component}
41604  *     container        {@link Ext.container.Container}
41605  *     cycle            {@link Ext.button.Cycle}
41606  *     dataview         {@link Ext.view.View}
41607  *     datepicker       {@link Ext.picker.Date}
41608  *     editor           {@link Ext.Editor}
41609  *     editorgrid       {@link Ext.grid.plugin.Editing}
41610  *     grid             {@link Ext.grid.Panel}
41611  *     multislider      {@link Ext.slider.Multi}
41612  *     panel            {@link Ext.panel.Panel}
41613  *     progressbar      {@link Ext.ProgressBar}
41614  *     slider           {@link Ext.slider.Single}
41615  *     splitbutton      {@link Ext.button.Split}
41616  *     tabpanel         {@link Ext.tab.Panel}
41617  *     treepanel        {@link Ext.tree.Panel}
41618  *     viewport         {@link Ext.container.Viewport}
41619  *     window           {@link Ext.window.Window}
41620  *
41621  *     Toolbar components
41622  *     ---------------------------------------
41623  *     pagingtoolbar    {@link Ext.toolbar.Paging}
41624  *     toolbar          {@link Ext.toolbar.Toolbar}
41625  *     tbfill           {@link Ext.toolbar.Fill}
41626  *     tbitem           {@link Ext.toolbar.Item}
41627  *     tbseparator      {@link Ext.toolbar.Separator}
41628  *     tbspacer         {@link Ext.toolbar.Spacer}
41629  *     tbtext           {@link Ext.toolbar.TextItem}
41630  *
41631  *     Menu components
41632  *     ---------------------------------------
41633  *     menu             {@link Ext.menu.Menu}
41634  *     menucheckitem    {@link Ext.menu.CheckItem}
41635  *     menuitem         {@link Ext.menu.Item}
41636  *     menuseparator    {@link Ext.menu.Separator}
41637  *     menutextitem     {@link Ext.menu.Item}
41638  *
41639  *     Form components
41640  *     ---------------------------------------
41641  *     form             {@link Ext.form.Panel}
41642  *     checkbox         {@link Ext.form.field.Checkbox}
41643  *     combo            {@link Ext.form.field.ComboBox}
41644  *     datefield        {@link Ext.form.field.Date}
41645  *     displayfield     {@link Ext.form.field.Display}
41646  *     field            {@link Ext.form.field.Base}
41647  *     fieldset         {@link Ext.form.FieldSet}
41648  *     hidden           {@link Ext.form.field.Hidden}
41649  *     htmleditor       {@link Ext.form.field.HtmlEditor}
41650  *     label            {@link Ext.form.Label}
41651  *     numberfield      {@link Ext.form.field.Number}
41652  *     radio            {@link Ext.form.field.Radio}
41653  *     radiogroup       {@link Ext.form.RadioGroup}
41654  *     textarea         {@link Ext.form.field.TextArea}
41655  *     textfield        {@link Ext.form.field.Text}
41656  *     timefield        {@link Ext.form.field.Time}
41657  *     trigger          {@link Ext.form.field.Trigger}
41658  *
41659  *     Chart components
41660  *     ---------------------------------------
41661  *     chart            {@link Ext.chart.Chart}
41662  *     barchart         {@link Ext.chart.series.Bar}
41663  *     columnchart      {@link Ext.chart.series.Column}
41664  *     linechart        {@link Ext.chart.series.Line}
41665  *     piechart         {@link Ext.chart.series.Pie}
41666  *
41667  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
41668  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
41669  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
41670  *
41671  *     @example
41672  *     Ext.create('Ext.Component', {
41673  *         html: 'Hello world!',
41674  *         width: 300,
41675  *         height: 200,
41676  *         padding: 20,
41677  *         style: {
41678  *             color: '#FFFFFF',
41679  *             backgroundColor:'#000000'
41680  *         },
41681  *         renderTo: Ext.getBody()
41682  *     });
41683  *
41684  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
41685  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
41686  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
41687  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
41688  *
41689  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
41690  */
41691 Ext.define('Ext.Component', {
41692
41693     /* Begin Definitions */
41694
41695     alias: ['widget.component', 'widget.box'],
41696
41697     extend: 'Ext.AbstractComponent',
41698
41699     requires: [
41700         'Ext.util.DelayedTask'
41701     ],
41702
41703     uses: [
41704         'Ext.Layer',
41705         'Ext.resizer.Resizer',
41706         'Ext.util.ComponentDragger'
41707     ],
41708
41709     mixins: {
41710         floating: 'Ext.util.Floating'
41711     },
41712
41713     statics: {
41714         // Collapse/expand directions
41715         DIRECTION_TOP: 'top',
41716         DIRECTION_RIGHT: 'right',
41717         DIRECTION_BOTTOM: 'bottom',
41718         DIRECTION_LEFT: 'left',
41719
41720         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
41721
41722         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
41723         // This includes dot, comma and whitespace
41724         INVALID_ID_CHARS_Re: /[\.,\s]/g
41725     },
41726
41727     /* End Definitions */
41728
41729     /**
41730      * @cfg {Boolean/Object} resizable
41731      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
41732      *
41733      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
41734      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
41735      * `{@link Ext.resizer.Resizer#dynamic}: false`
41736      */
41737
41738     /**
41739      * @cfg {String} resizeHandles
41740      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
41741      */
41742     resizeHandles: 'all',
41743
41744     /**
41745      * @cfg {Boolean} [autoScroll=false]
41746      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
41747      * `false` to clip any overflowing content.
41748      */
41749
41750     /**
41751      * @cfg {Boolean} floating
41752      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
41753      *
41754      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
41755      *
41756      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
41757      * the global {@link Ext.WindowManager ZIndexManager}
41758      *
41759      * ### Floating Components as child items of a Container
41760      *
41761      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
41762      * a ZIndexManager by examining the ownerCt chain.
41763      *
41764      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
41765      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
41766      * of its stack when the Component's {@link #toFront} method is called.
41767      *
41768      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
41769      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
41770      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
41771      * ancestor Container.
41772      *
41773      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
41774      * ZIndexManager}.
41775      *
41776      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
41777      * you explicitly {@link #show} them.
41778      *
41779      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
41780      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
41781      * not be set.
41782      */
41783     floating: false,
41784
41785     /**
41786      * @cfg {Boolean} toFrontOnShow
41787      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
41788      * floating component.
41789      */
41790     toFrontOnShow: true,
41791
41792     /**
41793      * @property {Ext.ZIndexManager} zIndexManager
41794      * Only present for {@link #floating} Components after they have been rendered.
41795      *
41796      * A reference to the ZIndexManager which is managing this Component's z-index.
41797      *
41798      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
41799      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
41800      *
41801      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
41802      * z-index stack.
41803      *
41804      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
41805      * programatically {@link Ext.Component#render rendered}.
41806      *
41807      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
41808      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
41809      * used.
41810      *
41811      * See {@link #floating} and {@link #floatParent}
41812      */
41813
41814     /**
41815      * @property {Ext.Container} floatParent
41816      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
41817      *
41818      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
41819      * property.
41820      *
41821      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
41822      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
41823      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
41824      * Components.
41825      *
41826      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
41827      * Window as its `floatParent`
41828      *
41829      * See {@link #floating} and {@link #zIndexManager}
41830      */
41831
41832     /**
41833      * @cfg {Boolean/Object} [draggable=false]
41834      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
41835      * the drag handle.
41836      *
41837      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
41838      * instantiated to perform dragging.
41839      *
41840      * For example to create a Component which may only be dragged around using a certain internal element as the drag
41841      * handle, use the delegate option:
41842      *
41843      *     new Ext.Component({
41844      *         constrain: true,
41845      *         floating: true,
41846      *         style: {
41847      *             backgroundColor: '#fff',
41848      *             border: '1px solid black'
41849      *         },
41850      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
41851      *         draggable: {
41852      *             delegate: 'h1'
41853      *         }
41854      *     }).show();
41855      */
41856
41857     /**
41858      * @cfg {Boolean} [maintainFlex=false]
41859      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
41860      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
41861      *
41862      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
41863      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
41864      */
41865
41866     hideMode: 'display',
41867     // Deprecate 5.0
41868     hideParent: false,
41869
41870     ariaRole: 'presentation',
41871
41872     bubbleEvents: [],
41873
41874     actionMode: 'el',
41875     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
41876
41877     //renderTpl: new Ext.XTemplate(
41878     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
41879     //        compiled: true,
41880     //        disableFormats: true
41881     //    }
41882     //),
41883
41884     /**
41885      * Creates new Component.
41886      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
41887      *
41888      * - **an element** : it is set as the internal element and its id used as the component id
41889      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
41890      * - **anything else** : it is assumed to be a standard config object and is applied to the component
41891      */
41892     constructor: function(config) {
41893         var me = this;
41894
41895         config = config || {};
41896         if (config.initialConfig) {
41897
41898             // Being initialized from an Ext.Action instance...
41899             if (config.isAction) {
41900                 me.baseAction = config;
41901             }
41902             config = config.initialConfig;
41903             // component cloning / action set up
41904         }
41905         else if (config.tagName || config.dom || Ext.isString(config)) {
41906             // element object
41907             config = {
41908                 applyTo: config,
41909                 id: config.id || config
41910             };
41911         }
41912
41913         me.callParent([config]);
41914
41915         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
41916         // register this Component as one of its items
41917         if (me.baseAction){
41918             me.baseAction.addComponent(me);
41919         }
41920     },
41921
41922     /**
41923      * The initComponent template method is an important initialization step for a Component. It is intended to be
41924      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
41925      * initComponent method of the class being created is called first, with each initComponent method
41926      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
41927      * if needed, override the constructor logic of the Component at any step in the hierarchy.
41928      *
41929      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
41930      * to ensure that the parent class' initComponent method is also called.
41931      *
41932      * The following example demonstrates using a dynamic string for the text of a button at the time of
41933      * instantiation of the class.
41934      *
41935      *     Ext.define('DynamicButtonText', {
41936      *         extend: 'Ext.button.Button',
41937      *
41938      *         initComponent: function() {
41939      *             this.text = new Date();
41940      *             this.renderTo = Ext.getBody();
41941      *             this.callParent();
41942      *         }
41943      *     });
41944      *
41945      *     Ext.onReady(function() {
41946      *         Ext.create('DynamicButtonText');
41947      *     });
41948      *
41949      * @template
41950      */
41951     initComponent: function() {
41952         var me = this;
41953
41954         me.callParent();
41955
41956         if (me.listeners) {
41957             me.on(me.listeners);
41958             delete me.listeners;
41959         }
41960         me.enableBubble(me.bubbleEvents);
41961         me.mons = [];
41962     },
41963
41964     // private
41965     afterRender: function() {
41966         var me = this,
41967             resizable = me.resizable;
41968
41969         if (me.floating) {
41970             me.makeFloating(me.floating);
41971         } else {
41972             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
41973         }
41974
41975         if (Ext.isDefined(me.autoScroll)) {
41976             me.setAutoScroll(me.autoScroll);
41977         }
41978         me.callParent();
41979
41980         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
41981             me.setPagePosition(me.pageX, me.pageY);
41982         }
41983
41984         if (resizable) {
41985             me.initResizable(resizable);
41986         }
41987
41988         if (me.draggable) {
41989             me.initDraggable();
41990         }
41991
41992         me.initAria();
41993     },
41994
41995     initAria: function() {
41996         var actionEl = this.getActionEl(),
41997             role = this.ariaRole;
41998         if (role) {
41999             actionEl.dom.setAttribute('role', role);
42000         }
42001     },
42002
42003     /**
42004      * Sets the overflow on the content element of the component.
42005      * @param {Boolean} scroll True to allow the Component to auto scroll.
42006      * @return {Ext.Component} this
42007      */
42008     setAutoScroll : function(scroll){
42009         var me = this,
42010             targetEl;
42011         scroll = !!scroll;
42012         if (me.rendered) {
42013             targetEl = me.getTargetEl();
42014             targetEl.setStyle('overflow', scroll ? 'auto' : '');
42015             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
42016                 // The scrollable container element must be non-statically positioned or IE6/7 will make
42017                 // positioned children stay in place rather than scrolling with the rest of the content
42018                 targetEl.position();
42019             }
42020         }
42021         me.autoScroll = scroll;
42022         return me;
42023     },
42024
42025     // private
42026     makeFloating : function(cfg){
42027         this.mixins.floating.constructor.call(this, cfg);
42028     },
42029
42030     initResizable: function(resizable) {
42031         var me = this;
42032
42033         resizable = Ext.apply({
42034             target: me,
42035             dynamic: false,
42036             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
42037             handles: me.resizeHandles
42038         }, resizable);
42039         resizable.target = me;
42040         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
42041     },
42042
42043     getDragEl: function() {
42044         return this.el;
42045     },
42046
42047     initDraggable: function() {
42048         var me = this,
42049             ddConfig = Ext.applyIf({
42050                 el: me.getDragEl(),
42051                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
42052             }, me.draggable);
42053
42054         // Add extra configs if Component is specified to be constrained
42055         if (me.constrain || me.constrainDelegate) {
42056             ddConfig.constrain = me.constrain;
42057             ddConfig.constrainDelegate = me.constrainDelegate;
42058         }
42059
42060         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
42061     },
42062
42063     /**
42064      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
42065      * method fires the {@link #move} event.
42066      * @param {Number} left The new left
42067      * @param {Number} top The new top
42068      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
42069      * animation configuration.
42070      * @return {Ext.Component} this
42071      */
42072     setPosition: function(x, y, animate) {
42073         var me = this,
42074             el = me.el,
42075             to = {},
42076             adj, adjX, adjY, xIsNumber, yIsNumber;
42077
42078         if (Ext.isArray(x)) {
42079             animate = y;
42080             y = x[1];
42081             x = x[0];
42082         }
42083         me.x = x;
42084         me.y = y;
42085
42086         if (!me.rendered) {
42087             return me;
42088         }
42089
42090         adj = me.adjustPosition(x, y);
42091         adjX = adj.x;
42092         adjY = adj.y;
42093         xIsNumber = Ext.isNumber(adjX);
42094         yIsNumber = Ext.isNumber(adjY);
42095
42096         if (xIsNumber || yIsNumber) {
42097             if (animate) {
42098                 if (xIsNumber) {
42099                     to.left = adjX;
42100                 }
42101                 if (yIsNumber) {
42102                     to.top = adjY;
42103                 }
42104
42105                 me.stopAnimation();
42106                 me.animate(Ext.apply({
42107                     duration: 1000,
42108                     listeners: {
42109                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
42110                     },
42111                     to: to
42112                 }, animate));
42113             }
42114             else {
42115                 if (!xIsNumber) {
42116                     el.setTop(adjY);
42117                 }
42118                 else if (!yIsNumber) {
42119                     el.setLeft(adjX);
42120                 }
42121                 else {
42122                     el.setLeftTop(adjX, adjY);
42123                 }
42124                 me.afterSetPosition(adjX, adjY);
42125             }
42126         }
42127         return me;
42128     },
42129
42130     /**
42131      * @private
42132      * @template
42133      * Template method called after a Component has been positioned.
42134      */
42135     afterSetPosition: function(ax, ay) {
42136         this.onPosition(ax, ay);
42137         this.fireEvent('move', this, ax, ay);
42138     },
42139
42140     /**
42141      * Displays component at specific xy position.
42142      * A floating component (like a menu) is positioned relative to its ownerCt if any.
42143      * Useful for popping up a context menu:
42144      *
42145      *     listeners: {
42146      *         itemcontextmenu: function(view, record, item, index, event, options) {
42147      *             Ext.create('Ext.menu.Menu', {
42148      *                 width: 100,
42149      *                 height: 100,
42150      *                 margin: '0 0 10 0',
42151      *                 items: [{
42152      *                     text: 'regular item 1'
42153      *                 },{
42154      *                     text: 'regular item 2'
42155      *                 },{
42156      *                     text: 'regular item 3'
42157      *                 }]
42158      *             }).showAt(event.getXY());
42159      *         }
42160      *     }
42161      *
42162      * @param {Number} x The new x position
42163      * @param {Number} y The new y position
42164      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42165      * animation configuration.
42166      */
42167     showAt: function(x, y, animate) {
42168         var me = this;
42169
42170         if (me.floating) {
42171             me.setPosition(x, y, animate);
42172         } else {
42173             me.setPagePosition(x, y, animate);
42174         }
42175         me.show();
42176     },
42177
42178     /**
42179      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
42180      * This method fires the {@link #move} event.
42181      * @param {Number} x The new x position
42182      * @param {Number} y The new y position
42183      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42184      * animation configuration.
42185      * @return {Ext.Component} this
42186      */
42187     setPagePosition: function(x, y, animate) {
42188         var me = this,
42189             p;
42190
42191         if (Ext.isArray(x)) {
42192             y = x[1];
42193             x = x[0];
42194         }
42195         me.pageX = x;
42196         me.pageY = y;
42197         if (me.floating && me.floatParent) {
42198             // Floating Components being positioned in their ownerCt have to be made absolute
42199             p = me.floatParent.getTargetEl().getViewRegion();
42200             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
42201                 x -= p.left;
42202             }
42203             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
42204                 y -= p.top;
42205             }
42206             me.setPosition(x, y, animate);
42207         }
42208         else {
42209             p = me.el.translatePoints(x, y);
42210             me.setPosition(p.left, p.top, animate);
42211         }
42212         return me;
42213     },
42214
42215     /**
42216      * Gets the current box measurements of the component's underlying element.
42217      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42218      * @return {Object} box An object in the format {x, y, width, height}
42219      */
42220     getBox : function(local){
42221         var pos = this.getPosition(local),
42222             size = this.getSize();
42223
42224         size.x = pos[0];
42225         size.y = pos[1];
42226         return size;
42227     },
42228
42229     /**
42230      * Sets the current box measurements of the component's underlying element.
42231      * @param {Object} box An object in the format {x, y, width, height}
42232      * @return {Ext.Component} this
42233      */
42234     updateBox : function(box){
42235         this.setSize(box.width, box.height);
42236         this.setPagePosition(box.x, box.y);
42237         return this;
42238     },
42239
42240     // Include margins
42241     getOuterSize: function() {
42242         var el = this.el;
42243         return {
42244             width: el.getWidth() + el.getMargin('lr'),
42245             height: el.getHeight() + el.getMargin('tb')
42246         };
42247     },
42248
42249     // private
42250     adjustPosition: function(x, y) {
42251
42252         // Floating Components being positioned in their ownerCt have to be made absolute
42253         if (this.floating && this.floatParent) {
42254             var o = this.floatParent.getTargetEl().getViewRegion();
42255             x += o.left;
42256             y += o.top;
42257         }
42258
42259         return {
42260             x: x,
42261             y: y
42262         };
42263     },
42264
42265     /**
42266      * Gets the current XY position of the component's underlying element.
42267      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42268      * @return {Number[]} The XY position of the element (e.g., [100, 200])
42269      */
42270     getPosition: function(local) {
42271         var me = this,
42272             el = me.el,
42273             xy,
42274             o;
42275
42276         // Floating Components which were just rendered with no ownerCt return local position.
42277         if ((local === true) || (me.floating && !me.floatParent)) {
42278             return [el.getLeft(true), el.getTop(true)];
42279         }
42280         xy = me.xy || el.getXY();
42281
42282         // Floating Components in an ownerCt have to have their positions made relative
42283         if (me.floating) {
42284             o = me.floatParent.getTargetEl().getViewRegion();
42285             xy[0] -= o.left;
42286             xy[1] -= o.top;
42287         }
42288         return xy;
42289     },
42290
42291     getId: function() {
42292         var me = this,
42293             xtype;
42294
42295         if (!me.id) {
42296             xtype = me.getXType();
42297             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
42298             me.id = xtype + '-' + me.getAutoId();
42299         }
42300         return me.id;
42301     },
42302
42303     onEnable: function() {
42304         var actionEl = this.getActionEl();
42305         actionEl.dom.removeAttribute('aria-disabled');
42306         actionEl.dom.disabled = false;
42307         this.callParent();
42308     },
42309
42310     onDisable: function() {
42311         var actionEl = this.getActionEl();
42312         actionEl.dom.setAttribute('aria-disabled', true);
42313         actionEl.dom.disabled = true;
42314         this.callParent();
42315     },
42316
42317     /**
42318      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
42319      *
42320      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
42321      * brought to the front of its {@link #zIndexManager z-index stack}.
42322      *
42323      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
42324      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
42325      * with `floating: true`.** The target from which the Component should animate from while opening.
42326      * @param {Function} [callback] A callback function to call after the Component is displayed.
42327      * Only necessary if animation was specified.
42328      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42329      * Defaults to this Component.
42330      * @return {Ext.Component} this
42331      */
42332     show: function(animateTarget, cb, scope) {
42333         var me = this;
42334
42335         if (me.rendered && me.isVisible()) {
42336             if (me.toFrontOnShow && me.floating) {
42337                 me.toFront();
42338             }
42339         } else if (me.fireEvent('beforeshow', me) !== false) {
42340             me.hidden = false;
42341
42342             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
42343             if (!me.rendered && (me.autoRender || me.floating)) {
42344                 me.doAutoRender();
42345             }
42346             if (me.rendered) {
42347                 me.beforeShow();
42348                 me.onShow.apply(me, arguments);
42349
42350                 // Notify any owning Container unless it's suspended.
42351                 // Floating Components do not participate in layouts.
42352                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42353                     me.ownerCt.doLayout();
42354                 }
42355                 me.afterShow.apply(me, arguments);
42356             }
42357         }
42358         return me;
42359     },
42360
42361     beforeShow: Ext.emptyFn,
42362
42363     // Private. Override in subclasses where more complex behaviour is needed.
42364     onShow: function() {
42365         var me = this;
42366
42367         me.el.show();
42368         me.callParent(arguments);
42369         if (me.floating && me.constrain) {
42370             me.doConstrain();
42371         }
42372     },
42373
42374     afterShow: function(animateTarget, cb, scope) {
42375         var me = this,
42376             fromBox,
42377             toBox,
42378             ghostPanel;
42379
42380         // Default to configured animate target if none passed
42381         animateTarget = animateTarget || me.animateTarget;
42382
42383         // Need to be able to ghost the Component
42384         if (!me.ghost) {
42385             animateTarget = null;
42386         }
42387         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
42388         if (animateTarget) {
42389             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42390             toBox = me.el.getBox();
42391             fromBox = animateTarget.getBox();
42392             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
42393             ghostPanel = me.ghost();
42394             ghostPanel.el.stopAnimation();
42395
42396             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
42397             ghostPanel.el.setX(-10000);
42398
42399             ghostPanel.el.animate({
42400                 from: fromBox,
42401                 to: toBox,
42402                 listeners: {
42403                     afteranimate: function() {
42404                         delete ghostPanel.componentLayout.lastComponentSize;
42405                         me.unghost();
42406                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
42407                         me.onShowComplete(cb, scope);
42408                     }
42409                 }
42410             });
42411         }
42412         else {
42413             me.onShowComplete(cb, scope);
42414         }
42415     },
42416
42417     onShowComplete: function(cb, scope) {
42418         var me = this;
42419         if (me.floating) {
42420             me.toFront();
42421         }
42422         Ext.callback(cb, scope || me);
42423         me.fireEvent('show', me);
42424     },
42425
42426     /**
42427      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
42428      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
42429      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
42430      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
42431      * @param {Function} [callback] A callback function to call after the Component is hidden.
42432      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42433      * Defaults to this Component.
42434      * @return {Ext.Component} this
42435      */
42436     hide: function() {
42437         var me = this;
42438
42439         // Clear the flag which is set if a floatParent was hidden while this is visible.
42440         // If a hide operation was subsequently called, that pending show must be hidden.
42441         me.showOnParentShow = false;
42442
42443         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
42444             me.hidden = true;
42445             if (me.rendered) {
42446                 me.onHide.apply(me, arguments);
42447
42448                 // Notify any owning Container unless it's suspended.
42449                 // Floating Components do not participate in layouts.
42450                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42451                     me.ownerCt.doLayout();
42452                 }
42453             }
42454         }
42455         return me;
42456     },
42457
42458     // Possibly animate down to a target element.
42459     onHide: function(animateTarget, cb, scope) {
42460         var me = this,
42461             ghostPanel,
42462             toBox;
42463
42464         // Default to configured animate target if none passed
42465         animateTarget = animateTarget || me.animateTarget;
42466
42467         // Need to be able to ghost the Component
42468         if (!me.ghost) {
42469             animateTarget = null;
42470         }
42471         // If we're animating, kick off an animation of the ghost down to the target
42472         if (animateTarget) {
42473             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42474             ghostPanel = me.ghost();
42475             ghostPanel.el.stopAnimation();
42476             toBox = animateTarget.getBox();
42477             toBox.width += 'px';
42478             toBox.height += 'px';
42479             ghostPanel.el.animate({
42480                 to: toBox,
42481                 listeners: {
42482                     afteranimate: function() {
42483                         delete ghostPanel.componentLayout.lastComponentSize;
42484                         ghostPanel.el.hide();
42485                         me.afterHide(cb, scope);
42486                     }
42487                 }
42488             });
42489         }
42490         me.el.hide();
42491         if (!animateTarget) {
42492             me.afterHide(cb, scope);
42493         }
42494     },
42495
42496     afterHide: function(cb, scope) {
42497         Ext.callback(cb, scope || this);
42498         this.fireEvent('hide', this);
42499     },
42500
42501     /**
42502      * @private
42503      * @template
42504      * Template method to contribute functionality at destroy time.
42505      */
42506     onDestroy: function() {
42507         var me = this;
42508
42509         // Ensure that any ancillary components are destroyed.
42510         if (me.rendered) {
42511             Ext.destroy(
42512                 me.proxy,
42513                 me.proxyWrap,
42514                 me.resizer
42515             );
42516             // Different from AbstractComponent
42517             if (me.actionMode == 'container' || me.removeMode == 'container') {
42518                 me.container.remove();
42519             }
42520         }
42521         delete me.focusTask;
42522         me.callParent();
42523     },
42524
42525     deleteMembers: function() {
42526         var args = arguments,
42527             len = args.length,
42528             i = 0;
42529         for (; i < len; ++i) {
42530             delete this[args[i]];
42531         }
42532     },
42533
42534     /**
42535      * Try to focus this component.
42536      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
42537      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
42538      * @return {Ext.Component} this
42539      */
42540     focus: function(selectText, delay) {
42541         var me = this,
42542                 focusEl;
42543
42544         if (delay) {
42545             if (!me.focusTask) {
42546                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
42547             }
42548             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
42549             return me;
42550         }
42551
42552         if (me.rendered && !me.isDestroyed) {
42553             // getFocusEl could return a Component.
42554             focusEl = me.getFocusEl();
42555             focusEl.focus();
42556             if (focusEl.dom && selectText === true) {
42557                 focusEl.dom.select();
42558             }
42559
42560             // Focusing a floating Component brings it to the front of its stack.
42561             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
42562             if (me.floating) {
42563                 me.toFront(true);
42564             }
42565         }
42566         return me;
42567     },
42568
42569     /**
42570      * @private
42571      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
42572      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
42573      * by the {@link #focus} method.
42574      * @returns {Ext.Element} the focus holing element.
42575      */
42576     getFocusEl: function() {
42577         return this.el;
42578     },
42579
42580     // private
42581     blur: function() {
42582         if (this.rendered) {
42583             this.getFocusEl().blur();
42584         }
42585         return this;
42586     },
42587
42588     getEl: function() {
42589         return this.el;
42590     },
42591
42592     // Deprecate 5.0
42593     getResizeEl: function() {
42594         return this.el;
42595     },
42596
42597     // Deprecate 5.0
42598     getPositionEl: function() {
42599         return this.el;
42600     },
42601
42602     // Deprecate 5.0
42603     getActionEl: function() {
42604         return this.el;
42605     },
42606
42607     // Deprecate 5.0
42608     getVisibilityEl: function() {
42609         return this.el;
42610     },
42611
42612     // Deprecate 5.0
42613     onResize: Ext.emptyFn,
42614
42615     // private
42616     getBubbleTarget: function() {
42617         return this.ownerCt;
42618     },
42619
42620     // private
42621     getContentTarget: function() {
42622         return this.el;
42623     },
42624
42625     /**
42626      * Clone the current component using the original config values passed into this instance by default.
42627      * @param {Object} overrides A new config containing any properties to override in the cloned version.
42628      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
42629      * @return {Ext.Component} clone The cloned copy of this component
42630      */
42631     cloneConfig: function(overrides) {
42632         overrides = overrides || {};
42633         var id = overrides.id || Ext.id(),
42634             cfg = Ext.applyIf(overrides, this.initialConfig),
42635             self;
42636
42637         cfg.id = id;
42638
42639         self = Ext.getClass(this);
42640
42641         // prevent dup id
42642         return new self(cfg);
42643     },
42644
42645     /**
42646      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
42647      * xtypes, see the {@link Ext.Component} header. Example usage:
42648      *
42649      *     var t = new Ext.form.field.Text();
42650      *     alert(t.getXType());  // alerts 'textfield'
42651      *
42652      * @return {String} The xtype
42653      */
42654     getXType: function() {
42655         return this.self.xtype;
42656     },
42657
42658     /**
42659      * Find a container above this component at any level by a custom function. If the passed function returns true, the
42660      * container will be returned.
42661      * @param {Function} fn The custom function to call with the arguments (container, this component).
42662      * @return {Ext.container.Container} The first Container for which the custom function returns true
42663      */
42664     findParentBy: function(fn) {
42665         var p;
42666
42667         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
42668         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
42669         return p || null;
42670     },
42671
42672     /**
42673      * Find a container above this component at any level by xtype or class
42674      *
42675      * See also the {@link Ext.Component#up up} method.
42676      *
42677      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
42678      * @return {Ext.container.Container} The first Container which matches the given xtype or class
42679      */
42680     findParentByType: function(xtype) {
42681         return Ext.isFunction(xtype) ?
42682             this.findParentBy(function(p) {
42683                 return p.constructor === xtype;
42684             })
42685         :
42686             this.up(xtype);
42687     },
42688
42689     /**
42690      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
42691      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
42692      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
42693      *
42694      * @param {Function} fn The function to call
42695      * @param {Object} [scope] The scope of the function. Defaults to current node.
42696      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
42697      * @return {Ext.Component} this
42698      */
42699     bubble: function(fn, scope, args) {
42700         var p = this;
42701         while (p) {
42702             if (fn.apply(scope || p, args || [p]) === false) {
42703                 break;
42704             }
42705             p = p.ownerCt;
42706         }
42707         return this;
42708     },
42709
42710     getProxy: function() {
42711         var me = this,
42712             target;
42713
42714         if (!me.proxy) {
42715             target = Ext.getBody();
42716             if (Ext.scopeResetCSS) {
42717                 me.proxyWrap = target = Ext.getBody().createChild({
42718                     cls: Ext.baseCSSPrefix + 'reset'
42719                 });
42720             }
42721             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
42722         }
42723         return me.proxy;
42724     }
42725
42726 });
42727
42728 /**
42729  * @class Ext.layout.container.AbstractContainer
42730  * @extends Ext.layout.Layout
42731  * Please refer to sub classes documentation
42732  * @private
42733  */
42734 Ext.define('Ext.layout.container.AbstractContainer', {
42735
42736     /* Begin Definitions */
42737
42738     extend: 'Ext.layout.Layout',
42739
42740     /* End Definitions */
42741
42742     type: 'container',
42743
42744     /**
42745      * @cfg {Boolean} bindToOwnerCtComponent
42746      * Flag to notify the ownerCt Component on afterLayout of a change
42747      */
42748     bindToOwnerCtComponent: false,
42749
42750     /**
42751      * @cfg {Boolean} bindToOwnerCtContainer
42752      * Flag to notify the ownerCt Container on afterLayout of a change
42753      */
42754     bindToOwnerCtContainer: false,
42755
42756     /**
42757      * @cfg {String} itemCls
42758      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
42759      * customized styles to the container or any of its children using standard CSS rules. See
42760      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
42761      * </p>
42762      */
42763
42764     /**
42765     * Set the size of an item within the Container.  We should always use setCalculatedSize.
42766     * @private
42767     */
42768     setItemSize: function(item, width, height) {
42769         if (Ext.isObject(width)) {
42770             height = width.height;
42771             width = width.width;
42772         }
42773         item.setCalculatedSize(width, height, this.owner);
42774     },
42775
42776     /**
42777      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
42778      * base class), or the layout phase (onLayout).</p>
42779      * @return {Ext.Component[]} of child components
42780      */
42781     getLayoutItems: function() {
42782         return this.owner && this.owner.items && this.owner.items.items || [];
42783     },
42784
42785     /**
42786      * Containers should not lay out child components when collapsed.
42787      */
42788     beforeLayout: function() {
42789         return !this.owner.collapsed && this.callParent(arguments);
42790     },
42791
42792     afterLayout: function() {
42793         this.owner.afterLayout(this);
42794     },
42795     /**
42796      * Returns the owner component's resize element.
42797      * @return {Ext.Element}
42798      */
42799      getTarget: function() {
42800          return this.owner.getTargetEl();
42801      },
42802     /**
42803      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
42804      * May be overridden in layout managers which implement an inner element.
42805      * @return {Ext.Element}
42806      */
42807      getRenderTarget: function() {
42808          return this.owner.getTargetEl();
42809      }
42810 });
42811
42812 /**
42813 * @class Ext.layout.container.Container
42814 * @extends Ext.layout.container.AbstractContainer
42815 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
42816 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
42817 */
42818 Ext.define('Ext.layout.container.Container', {
42819
42820     /* Begin Definitions */
42821
42822     extend: 'Ext.layout.container.AbstractContainer',
42823     alternateClassName: 'Ext.layout.ContainerLayout',
42824
42825     /* End Definitions */
42826
42827     layoutItem: function(item, box) {
42828         if (box) {
42829             item.doComponentLayout(box.width, box.height);
42830         } else {
42831             item.doComponentLayout();
42832         }
42833     },
42834
42835     getLayoutTargetSize : function() {
42836         var target = this.getTarget(),
42837             ret;
42838
42839         if (target) {
42840             ret = target.getViewSize();
42841
42842             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
42843             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
42844             // with getViewSize
42845             if (Ext.isIE && ret.width == 0){
42846                 ret = target.getStyleSize();
42847             }
42848
42849             ret.width -= target.getPadding('lr');
42850             ret.height -= target.getPadding('tb');
42851         }
42852         return ret;
42853     },
42854
42855     beforeLayout: function() {
42856         if (this.owner.beforeLayout(arguments) !== false) {
42857             return this.callParent(arguments);
42858         }
42859         else {
42860             return false;
42861         }
42862     },
42863
42864     /**
42865      * @protected
42866      * Returns all items that are rendered
42867      * @return {Array} All matching items
42868      */
42869     getRenderedItems: function() {
42870         var me = this,
42871             target = me.getTarget(),
42872             items = me.getLayoutItems(),
42873             ln = items.length,
42874             renderedItems = [],
42875             i, item;
42876
42877         for (i = 0; i < ln; i++) {
42878             item = items[i];
42879             if (item.rendered && me.isValidParent(item, target, i)) {
42880                 renderedItems.push(item);
42881             }
42882         }
42883
42884         return renderedItems;
42885     },
42886
42887     /**
42888      * @protected
42889      * Returns all items that are both rendered and visible
42890      * @return {Array} All matching items
42891      */
42892     getVisibleItems: function() {
42893         var target   = this.getTarget(),
42894             items = this.getLayoutItems(),
42895             ln = items.length,
42896             visibleItems = [],
42897             i, item;
42898
42899         for (i = 0; i < ln; i++) {
42900             item = items[i];
42901             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
42902                 visibleItems.push(item);
42903             }
42904         }
42905
42906         return visibleItems;
42907     }
42908 });
42909 /**
42910  * @class Ext.layout.container.Auto
42911  * @extends Ext.layout.container.Container
42912  *
42913  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
42914  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
42915  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
42916  * to any child containers.
42917  *
42918  *     @example
42919  *     Ext.create('Ext.Panel', {
42920  *         width: 500,
42921  *         height: 280,
42922  *         title: "AutoLayout Panel",
42923  *         layout: 'auto',
42924  *         renderTo: document.body,
42925  *         items: [{
42926  *             xtype: 'panel',
42927  *             title: 'Top Inner Panel',
42928  *             width: '75%',
42929  *             height: 90
42930  *         },
42931  *         {
42932  *             xtype: 'panel',
42933  *             title: 'Bottom Inner Panel',
42934  *             width: '75%',
42935  *             height: 90
42936  *         }]
42937  *     });
42938  */
42939 Ext.define('Ext.layout.container.Auto', {
42940
42941     /* Begin Definitions */
42942
42943     alias: ['layout.auto', 'layout.autocontainer'],
42944
42945     extend: 'Ext.layout.container.Container',
42946
42947     /* End Definitions */
42948
42949     type: 'autocontainer',
42950
42951     bindToOwnerCtComponent: true,
42952
42953     // @private
42954     onLayout : function(owner, target) {
42955         var me = this,
42956             items = me.getLayoutItems(),
42957             ln = items.length,
42958             i;
42959
42960         // Ensure the Container is only primed with the clear element if there are child items.
42961         if (ln) {
42962             // Auto layout uses natural HTML flow to arrange the child items.
42963             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
42964             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
42965             if (!me.clearEl) {
42966                 me.clearEl = me.getRenderTarget().createChild({
42967                     cls: Ext.baseCSSPrefix + 'clear',
42968                     role: 'presentation'
42969                 });
42970             }
42971
42972             // Auto layout allows CSS to size its child items.
42973             for (i = 0; i < ln; i++) {
42974                 me.setItemSize(items[i]);
42975             }
42976         }
42977     },
42978
42979     configureItem: function(item) {
42980         this.callParent(arguments);
42981
42982         // Auto layout does not manage any dimensions.
42983         item.layoutManagedHeight = 2;
42984         item.layoutManagedWidth = 2;
42985     }
42986 });
42987 /**
42988  * @class Ext.container.AbstractContainer
42989  * @extends Ext.Component
42990  * An abstract base class which provides shared methods for Containers across the Sencha product line.
42991  * @private
42992  */
42993 Ext.define('Ext.container.AbstractContainer', {
42994
42995     /* Begin Definitions */
42996
42997     extend: 'Ext.Component',
42998
42999     requires: [
43000         'Ext.util.MixedCollection',
43001         'Ext.layout.container.Auto',
43002         'Ext.ZIndexManager'
43003     ],
43004
43005     /* End Definitions */
43006     /**
43007      * @cfg {String/Object} layout
43008      * <p><b>Important</b>: In order for child items to be correctly sized and
43009      * positioned, typically a layout manager <b>must</b> be specified through
43010      * the <code>layout</code> configuration option.</p>
43011      * <p>The sizing and positioning of child {@link #items} is the responsibility of
43012      * the Container's layout manager which creates and manages the type of layout
43013      * you have in mind.  For example:</p>
43014      * <p>If the {@link #layout} configuration is not explicitly specified for
43015      * a general purpose container (e.g. Container or Panel) the
43016      * {@link Ext.layout.container.Auto default layout manager} will be used
43017      * which does nothing but render child components sequentially into the
43018      * Container (no sizing or positioning will be performed in this situation).</p>
43019      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
43020      * <div><ul class="mdetail-params">
43021      * <li><u>Specify as an Object</u></li>
43022      * <div><ul class="mdetail-params">
43023      * <li>Example usage:</li>
43024      * <pre><code>
43025 layout: {
43026     type: 'vbox',
43027     align: 'left'
43028 }
43029        </code></pre>
43030      *
43031      * <li><code><b>type</b></code></li>
43032      * <br/><p>The layout type to be used for this container.  If not specified,
43033      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
43034      * <p>Valid layout <code>type</code> values are:</p>
43035      * <div class="sub-desc"><ul class="mdetail-params">
43036      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
43037      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
43038      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
43039      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
43040      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
43041      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
43042      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
43043      * </ul></div>
43044      *
43045      * <li>Layout specific configuration properties</li>
43046      * <p>Additional layout specific configuration properties may also be
43047      * specified. For complete details regarding the valid config options for
43048      * each layout type, see the layout class corresponding to the <code>type</code>
43049      * specified.</p>
43050      *
43051      * </ul></div>
43052      *
43053      * <li><u>Specify as a String</u></li>
43054      * <div><ul class="mdetail-params">
43055      * <li>Example usage:</li>
43056      * <pre><code>
43057 layout: 'vbox'
43058        </code></pre>
43059      * <li><code><b>layout</b></code></li>
43060      * <p>The layout <code>type</code> to be used for this container (see list
43061      * of valid layout type values above).</p>
43062      * <p>Additional layout specific configuration properties. For complete
43063      * details regarding the valid config options for each layout type, see the
43064      * layout class corresponding to the <code>layout</code> specified.</p>
43065      * </ul></div></ul></div>
43066      */
43067
43068     /**
43069      * @cfg {String/Number} activeItem
43070      * A string component id or the numeric index of the component that should be initially activated within the
43071      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
43072      * item in the container's collection).  activeItem only applies to layout styles that can display
43073      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
43074      */
43075     /**
43076      * @cfg {Object/Object[]} items
43077      * <p>A single item, or an array of child Components to be added to this container</p>
43078      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
43079      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
43080      * <p>Example:</p>
43081      * <pre><code>
43082 // specifying a single item
43083 items: {...},
43084 layout: 'fit',    // The single items is sized to fit
43085
43086 // specifying multiple items
43087 items: [{...}, {...}],
43088 layout: 'hbox', // The items are arranged horizontally
43089        </code></pre>
43090      * <p>Each item may be:</p>
43091      * <ul>
43092      * <li>A {@link Ext.Component Component}</li>
43093      * <li>A Component configuration object</li>
43094      * </ul>
43095      * <p>If a configuration object is specified, the actual type of Component to be
43096      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
43097      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
43098      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
43099      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
43100      * <p><b>Notes</b>:</p>
43101      * <p>Ext uses lazy rendering. Child Components will only be rendered
43102      * should it become necessary. Items are automatically laid out when they are first
43103      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
43104      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
43105      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
43106      */
43107     /**
43108      * @cfg {Object/Function} defaults
43109      * This option is a means of applying default settings to all added items whether added through the {@link #items}
43110      * config or via the {@link #add} or {@link #insert} methods.
43111      *
43112      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
43113      * existing properties in the item (see {@link Ext#applyIf}).
43114      *
43115      * If the defaults option is specified as a function, then the function will be called using this Container as the
43116      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
43117      * from that call is then applied to the item as default properties.
43118      *
43119      * For example, to automatically apply padding to the body of each of a set of
43120      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
43121      *
43122      * Usage:
43123      *
43124      *     defaults: { // defaults are applied to items, not the container
43125      *         autoScroll: true
43126      *     },
43127      *     items: [
43128      *         // default will not be applied here, panel1 will be autoScroll: false
43129      *         {
43130      *             xtype: 'panel',
43131      *             id: 'panel1',
43132      *             autoScroll: false
43133      *         },
43134      *         // this component will have autoScroll: true
43135      *         new Ext.panel.Panel({
43136      *             id: 'panel2'
43137      *         })
43138      *     ]
43139      */
43140
43141     /** @cfg {Boolean} suspendLayout
43142      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
43143      * as multiple arguments or an array.
43144      */
43145     suspendLayout : false,
43146
43147     /** @cfg {Boolean} autoDestroy
43148      * If true the container will automatically destroy any contained component that is removed from it, else
43149      * destruction must be handled manually.
43150      * Defaults to true.
43151      */
43152     autoDestroy : true,
43153
43154      /** @cfg {String} defaultType
43155       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
43156       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
43157       * <p>Defaults to <code>'panel'</code>.</p>
43158       */
43159     defaultType: 'panel',
43160
43161     isContainer : true,
43162
43163     /**
43164      * The number of container layout calls made on this object.
43165      * @property layoutCounter
43166      * @type {Number}
43167      * @private
43168      */
43169     layoutCounter : 0,
43170
43171     baseCls: Ext.baseCSSPrefix + 'container',
43172
43173     /**
43174      * @cfg {String[]} bubbleEvents
43175      * <p>An array of events that, when fired, should be bubbled to any parent container.
43176      * See {@link Ext.util.Observable#enableBubble}.
43177      * Defaults to <code>['add', 'remove']</code>.
43178      */
43179     bubbleEvents: ['add', 'remove'],
43180
43181     // @private
43182     initComponent : function(){
43183         var me = this;
43184         me.addEvents(
43185             /**
43186              * @event afterlayout
43187              * Fires when the components in this container are arranged by the associated layout manager.
43188              * @param {Ext.container.Container} this
43189              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
43190              */
43191             'afterlayout',
43192             /**
43193              * @event beforeadd
43194              * Fires before any {@link Ext.Component} is added or inserted into the container.
43195              * A handler can return false to cancel the add.
43196              * @param {Ext.container.Container} this
43197              * @param {Ext.Component} component The component being added
43198              * @param {Number} index The index at which the component will be added to the container's items collection
43199              */
43200             'beforeadd',
43201             /**
43202              * @event beforeremove
43203              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
43204              * false to cancel the remove.
43205              * @param {Ext.container.Container} this
43206              * @param {Ext.Component} component The component being removed
43207              */
43208             'beforeremove',
43209             /**
43210              * @event add
43211              * @bubbles
43212              * Fires after any {@link Ext.Component} is added or inserted into the container.
43213              * @param {Ext.container.Container} this
43214              * @param {Ext.Component} component The component that was added
43215              * @param {Number} index The index at which the component was added to the container's items collection
43216              */
43217             'add',
43218             /**
43219              * @event remove
43220              * @bubbles
43221              * Fires after any {@link Ext.Component} is removed from the container.
43222              * @param {Ext.container.Container} this
43223              * @param {Ext.Component} component The component that was removed
43224              */
43225             'remove'
43226         );
43227
43228         // layoutOnShow stack
43229         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
43230         me.callParent();
43231         me.initItems();
43232     },
43233
43234     // @private
43235     initItems : function() {
43236         var me = this,
43237             items = me.items;
43238
43239         /**
43240          * The MixedCollection containing all the child items of this container.
43241          * @property items
43242          * @type Ext.util.MixedCollection
43243          */
43244         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
43245
43246         if (items) {
43247             if (!Ext.isArray(items)) {
43248                 items = [items];
43249             }
43250
43251             me.add(items);
43252         }
43253     },
43254
43255     // @private
43256     afterRender : function() {
43257         this.getLayout();
43258         this.callParent();
43259     },
43260
43261     renderChildren: function () {
43262         var me = this,
43263             layout = me.getLayout();
43264
43265         me.callParent();
43266         // this component's elements exist, so now create the child components' elements
43267
43268         if (layout) {
43269             me.suspendLayout = true;
43270             layout.renderChildren();
43271             delete me.suspendLayout;
43272         }
43273     },
43274
43275     // @private
43276     setLayout : function(layout) {
43277         var currentLayout = this.layout;
43278
43279         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
43280             currentLayout.setOwner(null);
43281         }
43282
43283         this.layout = layout;
43284         layout.setOwner(this);
43285     },
43286
43287     /**
43288      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
43289      * If a layout has not been instantiated yet, that is done first
43290      * @return {Ext.layout.container.AbstractContainer} The layout
43291      */
43292     getLayout : function() {
43293         var me = this;
43294         if (!me.layout || !me.layout.isLayout) {
43295             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
43296         }
43297
43298         return me.layout;
43299     },
43300
43301     /**
43302      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
43303      * form most cases.
43304      * @return {Ext.container.Container} this
43305      */
43306     doLayout : function() {
43307         var me = this,
43308             layout = me.getLayout();
43309
43310         if (me.rendered && layout && !me.suspendLayout) {
43311             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
43312             if (!me.isFixedWidth() || !me.isFixedHeight()) {
43313                 // Only run the ComponentLayout if it is not already in progress
43314                 if (me.componentLayout.layoutBusy !== true) {
43315                     me.doComponentLayout();
43316                     if (me.componentLayout.layoutCancelled === true) {
43317                         layout.layout();
43318                     }
43319                 }
43320             }
43321             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
43322             else {
43323                 // Only run the ContainerLayout if it is not already in progress
43324                 if (layout.layoutBusy !== true) {
43325                     layout.layout();
43326                 }
43327             }
43328         }
43329
43330         return me;
43331     },
43332
43333     // @private
43334     afterLayout : function(layout) {
43335         ++this.layoutCounter;
43336         this.fireEvent('afterlayout', this, layout);
43337     },
43338
43339     // @private
43340     prepareItems : function(items, applyDefaults) {
43341         if (!Ext.isArray(items)) {
43342             items = [items];
43343         }
43344
43345         // Make sure defaults are applied and item is initialized
43346         var i = 0,
43347             len = items.length,
43348             item;
43349
43350         for (; i < len; i++) {
43351             item = items[i];
43352             if (applyDefaults) {
43353                 item = this.applyDefaults(item);
43354             }
43355             items[i] = this.lookupComponent(item);
43356         }
43357         return items;
43358     },
43359
43360     // @private
43361     applyDefaults : function(config) {
43362         var defaults = this.defaults;
43363
43364         if (defaults) {
43365             if (Ext.isFunction(defaults)) {
43366                 defaults = defaults.call(this, config);
43367             }
43368
43369             if (Ext.isString(config)) {
43370                 config = Ext.ComponentManager.get(config);
43371             }
43372             Ext.applyIf(config, defaults);
43373         }
43374
43375         return config;
43376     },
43377
43378     // @private
43379     lookupComponent : function(comp) {
43380         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
43381     },
43382
43383     // @private
43384     createComponent : function(config, defaultType) {
43385         // // add in ownerCt at creation time but then immediately
43386         // // remove so that onBeforeAdd can handle it
43387         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
43388         //
43389         // delete component.initialConfig.ownerCt;
43390         // delete component.ownerCt;
43391
43392         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
43393     },
43394
43395     // @private - used as the key lookup function for the items collection
43396     getComponentId : function(comp) {
43397         return comp.getItemId();
43398     },
43399
43400     /**
43401
43402 Adds {@link Ext.Component Component}(s) to this Container.
43403
43404 ##Description:##
43405
43406 - Fires the {@link #beforeadd} event before adding.
43407 - The Container's {@link #defaults default config values} will be applied
43408   accordingly (see `{@link #defaults}` for details).
43409 - Fires the `{@link #add}` event after the component has been added.
43410
43411 ##Notes:##
43412
43413 If the Container is __already rendered__ when `add`
43414 is called, it will render the newly added Component into its content area.
43415
43416 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
43417 will recalculate its internal layout at this time too.
43418
43419 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
43420
43421 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
43422
43423     tb = new {@link Ext.toolbar.Toolbar}({
43424         renderTo: document.body
43425     });  // toolbar is rendered
43426     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
43427
43428 ##Warning:##
43429
43430 Components directly managed by the BorderLayout layout manager
43431 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
43432 for more details.
43433
43434      * @param {Ext.Component[]/Ext.Component...} component
43435      * Either one or more Components to add or an Array of Components to add.
43436      * See `{@link #items}` for additional information.
43437      *
43438      * @return {Ext.Component[]/Ext.Component} The Components that were added.
43439      * @markdown
43440      */
43441     add : function() {
43442         var me = this,
43443             args = Array.prototype.slice.call(arguments),
43444             hasMultipleArgs,
43445             items,
43446             results = [],
43447             i,
43448             ln,
43449             item,
43450             index = -1,
43451             cmp;
43452
43453         if (typeof args[0] == 'number') {
43454             index = args.shift();
43455         }
43456
43457         hasMultipleArgs = args.length > 1;
43458         if (hasMultipleArgs || Ext.isArray(args[0])) {
43459
43460             items = hasMultipleArgs ? args : args[0];
43461             // Suspend Layouts while we add multiple items to the container
43462             me.suspendLayout = true;
43463             for (i = 0, ln = items.length; i < ln; i++) {
43464                 item = items[i];
43465
43466
43467                 if (index != -1) {
43468                     item = me.add(index + i, item);
43469                 } else {
43470                     item = me.add(item);
43471                 }
43472                 results.push(item);
43473             }
43474             // Resume Layouts now that all items have been added and do a single layout for all the items just added
43475             me.suspendLayout = false;
43476             me.doLayout();
43477             return results;
43478         }
43479
43480         cmp = me.prepareItems(args[0], true)[0];
43481
43482         // Floating Components are not added into the items collection
43483         // But they do get an upward ownerCt link so that they can traverse
43484         // up to their z-index parent.
43485         if (cmp.floating) {
43486             cmp.onAdded(me, index);
43487         } else {
43488             index = (index !== -1) ? index : me.items.length;
43489             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
43490                 me.items.insert(index, cmp);
43491                 cmp.onAdded(me, index);
43492                 me.onAdd(cmp, index);
43493                 me.fireEvent('add', me, cmp, index);
43494             }
43495             me.doLayout();
43496         }
43497         return cmp;
43498     },
43499
43500     onAdd : Ext.emptyFn,
43501     onRemove : Ext.emptyFn,
43502
43503     /**
43504      * Inserts a Component into this Container at a specified index. Fires the
43505      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
43506      * Component has been inserted.
43507      * @param {Number} index The index at which the Component will be inserted
43508      * into the Container's items collection
43509      * @param {Ext.Component} component The child Component to insert.<br><br>
43510      * Ext uses lazy rendering, and will only render the inserted Component should
43511      * it become necessary.<br><br>
43512      * A Component config object may be passed in order to avoid the overhead of
43513      * constructing a real Component object if lazy rendering might mean that the
43514      * inserted Component will not be rendered immediately. To take advantage of
43515      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
43516      * property to the registered type of the Component wanted.<br><br>
43517      * For a list of all available xtypes, see {@link Ext.Component}.
43518      * @return {Ext.Component} component The Component (or config object) that was
43519      * inserted with the Container's default config values applied.
43520      */
43521     insert : function(index, comp) {
43522         return this.add(index, comp);
43523     },
43524
43525     /**
43526      * Moves a Component within the Container
43527      * @param {Number} fromIdx The index the Component you wish to move is currently at.
43528      * @param {Number} toIdx The new index for the Component.
43529      * @return {Ext.Component} component The Component (or config object) that was moved.
43530      */
43531     move : function(fromIdx, toIdx) {
43532         var items = this.items,
43533             item;
43534         item = items.removeAt(fromIdx);
43535         if (item === false) {
43536             return false;
43537         }
43538         items.insert(toIdx, item);
43539         this.doLayout();
43540         return item;
43541     },
43542
43543     // @private
43544     onBeforeAdd : function(item) {
43545         var me = this;
43546
43547         if (item.ownerCt) {
43548             item.ownerCt.remove(item, false);
43549         }
43550
43551         if (me.border === false || me.border === 0) {
43552             item.border = (item.border === true);
43553         }
43554     },
43555
43556     /**
43557      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
43558      * the {@link #remove} event after the component has been removed.
43559      * @param {Ext.Component/String} component The component reference or id to remove.
43560      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43561      * Defaults to the value of this Container's {@link #autoDestroy} config.
43562      * @return {Ext.Component} component The Component that was removed.
43563      */
43564     remove : function(comp, autoDestroy) {
43565         var me = this,
43566             c = me.getComponent(comp);
43567
43568         if (c && me.fireEvent('beforeremove', me, c) !== false) {
43569             me.doRemove(c, autoDestroy);
43570             me.fireEvent('remove', me, c);
43571         }
43572
43573         return c;
43574     },
43575
43576     // @private
43577     doRemove : function(component, autoDestroy) {
43578         var me = this,
43579             layout = me.layout,
43580             hasLayout = layout && me.rendered;
43581
43582         me.items.remove(component);
43583         component.onRemoved();
43584
43585         if (hasLayout) {
43586             layout.onRemove(component);
43587         }
43588
43589         me.onRemove(component, autoDestroy);
43590
43591         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
43592             component.destroy();
43593         }
43594
43595         if (hasLayout && !autoDestroy) {
43596             layout.afterRemove(component);
43597         }
43598
43599         if (!me.destroying) {
43600             me.doLayout();
43601         }
43602     },
43603
43604     /**
43605      * Removes all components from this container.
43606      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43607      * Defaults to the value of this Container's {@link #autoDestroy} config.
43608      * @return {Ext.Component[]} Array of the destroyed components
43609      */
43610     removeAll : function(autoDestroy) {
43611         var me = this,
43612             removeItems = me.items.items.slice(),
43613             items = [],
43614             i = 0,
43615             len = removeItems.length,
43616             item;
43617
43618         // Suspend Layouts while we remove multiple items from the container
43619         me.suspendLayout = true;
43620         for (; i < len; i++) {
43621             item = removeItems[i];
43622             me.remove(item, autoDestroy);
43623
43624             if (item.ownerCt !== me) {
43625                 items.push(item);
43626             }
43627         }
43628
43629         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
43630         me.suspendLayout = false;
43631         if (len) {
43632             me.doLayout();
43633         }
43634         return items;
43635     },
43636
43637     // Used by ComponentQuery to retrieve all of the items
43638     // which can potentially be considered a child of this Container.
43639     // This should be overriden by components which have child items
43640     // that are not contained in items. For example dockedItems, menu, etc
43641     // IMPORTANT note for maintainers:
43642     //  Items are returned in tree traversal order. Each item is appended to the result array
43643     //  followed by the results of that child's getRefItems call.
43644     //  Floating child items are appended after internal child items.
43645     getRefItems : function(deep) {
43646         var me = this,
43647             items = me.items.items,
43648             len = items.length,
43649             i = 0,
43650             item,
43651             result = [];
43652
43653         for (; i < len; i++) {
43654             item = items[i];
43655             result.push(item);
43656             if (deep && item.getRefItems) {
43657                 result.push.apply(result, item.getRefItems(true));
43658             }
43659         }
43660
43661         // Append floating items to the list.
43662         // These will only be present after they are rendered.
43663         if (me.floatingItems && me.floatingItems.accessList) {
43664             result.push.apply(result, me.floatingItems.accessList);
43665         }
43666
43667         return result;
43668     },
43669
43670     /**
43671      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
43672      * each component. The scope (<code>this</code> reference) of the
43673      * function call will be the scope provided or the current component. The arguments to the function
43674      * will be the args provided or the current component. If the function returns false at any point,
43675      * the cascade is stopped on that branch.
43676      * @param {Function} fn The function to call
43677      * @param {Object} [scope] The scope of the function (defaults to current component)
43678      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
43679      * @return {Ext.Container} this
43680      */
43681     cascade : function(fn, scope, origArgs){
43682         var me = this,
43683             cs = me.items ? me.items.items : [],
43684             len = cs.length,
43685             i = 0,
43686             c,
43687             args = origArgs ? origArgs.concat(me) : [me],
43688             componentIndex = args.length - 1;
43689
43690         if (fn.apply(scope || me, args) !== false) {
43691             for(; i < len; i++){
43692                 c = cs[i];
43693                 if (c.cascade) {
43694                     c.cascade(fn, scope, origArgs);
43695                 } else {
43696                     args[componentIndex] = c;
43697                     fn.apply(scope || cs, args);
43698                 }
43699             }
43700         }
43701         return this;
43702     },
43703
43704     /**
43705      * Examines this container's <code>{@link #items}</code> <b>property</b>
43706      * and gets a direct child component of this container.
43707      * @param {String/Number} comp This parameter may be any of the following:
43708      * <div><ul class="mdetail-params">
43709      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
43710      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
43711      * <li>a <b><code>Number</code></b> : representing the position of the child component
43712      * within the <code>{@link #items}</code> <b>property</b></li>
43713      * </ul></div>
43714      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
43715      * @return Ext.Component The component (if found).
43716      */
43717     getComponent : function(comp) {
43718         if (Ext.isObject(comp)) {
43719             comp = comp.getItemId();
43720         }
43721
43722         return this.items.get(comp);
43723     },
43724
43725     /**
43726      * Retrieves all descendant components which match the passed selector.
43727      * Executes an Ext.ComponentQuery.query using this container as its root.
43728      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
43729      * If no selector is specified all items will be returned.
43730      * @return {Ext.Component[]} Components which matched the selector
43731      */
43732     query : function(selector) {
43733         selector = selector || '*';
43734         return Ext.ComponentQuery.query(selector, this);
43735     },
43736
43737     /**
43738      * Retrieves the first direct child of this container which matches the passed selector.
43739      * The passed in selector must comply with an Ext.ComponentQuery selector.
43740      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43741      * specified, the first child will be returned.
43742      * @return Ext.Component
43743      */
43744     child : function(selector) {
43745         selector = selector || '';
43746         return this.query('> ' + selector)[0] || null;
43747     },
43748
43749     /**
43750      * Retrieves the first descendant of this container which matches the passed selector.
43751      * The passed in selector must comply with an Ext.ComponentQuery selector.
43752      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43753      * specified, the first child will be returned.
43754      * @return Ext.Component
43755      */
43756     down : function(selector) {
43757         return this.query(selector)[0] || null;
43758     },
43759
43760     // inherit docs
43761     show : function() {
43762         this.callParent(arguments);
43763         this.performDeferredLayouts();
43764         return this;
43765     },
43766
43767     // Lay out any descendant containers who queued a layout operation during the time this was hidden
43768     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
43769     performDeferredLayouts: function() {
43770         var layoutCollection = this.layoutOnShow,
43771             ln = layoutCollection.getCount(),
43772             i = 0,
43773             needsLayout,
43774             item;
43775
43776         for (; i < ln; i++) {
43777             item = layoutCollection.get(i);
43778             needsLayout = item.needsLayout;
43779
43780             if (Ext.isObject(needsLayout)) {
43781                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
43782             }
43783         }
43784         layoutCollection.clear();
43785     },
43786
43787     //@private
43788     // Enable all immediate children that was previously disabled
43789     onEnable: function() {
43790         Ext.Array.each(this.query('[isFormField]'), function(item) {
43791             if (item.resetDisable) {
43792                 item.enable();
43793                 delete item.resetDisable;
43794             }
43795         });
43796         this.callParent();
43797     },
43798
43799     // @private
43800     // Disable all immediate children that was previously disabled
43801     onDisable: function() {
43802         Ext.Array.each(this.query('[isFormField]'), function(item) {
43803             if (item.resetDisable !== false && !item.disabled) {
43804                 item.disable();
43805                 item.resetDisable = true;
43806             }
43807         });
43808         this.callParent();
43809     },
43810
43811     /**
43812      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
43813      * from being executed.
43814      */
43815     beforeLayout: function() {
43816         return true;
43817     },
43818
43819     // @private
43820     beforeDestroy : function() {
43821         var me = this,
43822             items = me.items,
43823             c;
43824
43825         if (items) {
43826             while ((c = items.first())) {
43827                 me.doRemove(c, true);
43828             }
43829         }
43830
43831         Ext.destroy(
43832             me.layout
43833         );
43834         me.callParent();
43835     }
43836 });
43837
43838 /**
43839  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
43840  * containing items, namely adding, inserting and removing items.
43841  *
43842  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
43843  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
43844  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
43845  * {@link Ext.Component#autoEl autoEl} config option.
43846  *
43847  * The code below illustrates how to explicitly create a Container:
43848  *
43849  *     @example
43850  *     // Explicitly create a Container
43851  *     Ext.create('Ext.container.Container', {
43852  *         layout: {
43853  *             type: 'hbox'
43854  *         },
43855  *         width: 400,
43856  *         renderTo: Ext.getBody(),
43857  *         border: 1,
43858  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
43859  *         defaults: {
43860  *             labelWidth: 80,
43861  *             // implicitly create Container by specifying xtype
43862  *             xtype: 'datefield',
43863  *             flex: 1,
43864  *             style: {
43865  *                 padding: '10px'
43866  *             }
43867  *         },
43868  *         items: [{
43869  *             xtype: 'datefield',
43870  *             name: 'startDate',
43871  *             fieldLabel: 'Start date'
43872  *         },{
43873  *             xtype: 'datefield',
43874  *             name: 'endDate',
43875  *             fieldLabel: 'End date'
43876  *         }]
43877  *     });
43878  *
43879  * ## Layout
43880  *
43881  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
43882  * the Container using the `{@link #layout}` configuration property.
43883  *
43884  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
43885  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
43886  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
43887  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
43888  * inside the Container, and **does not apply any sizing** at all.
43889  *
43890  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
43891  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
43892  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
43893  * any way when the Container is resized.
43894  *
43895  * Certain layout managers allow dynamic addition of child components. Those that do include
43896  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
43897  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
43898  *
43899  *     //  Create the GridPanel.
43900  *     var myNewGrid = new Ext.grid.Panel({
43901  *         store: myStore,
43902  *         headers: myHeaders,
43903  *         title: 'Results', // the title becomes the title of the tab
43904  *     });
43905  *
43906  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
43907  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
43908  *
43909  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
43910  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
43911  * Ext.layout.container.Fit fit} exactly into its client area.
43912  *
43913  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
43914  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
43915  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
43916  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
43917  * GridPanel will not be sized as expected.
43918  *
43919  * ## Adding via remote configuration
43920  *
43921  * A server side script can be used to add Components which are generated dynamically on the server. An example of
43922  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
43923  *
43924  *     // execute an Ajax request to invoke server side script:
43925  *     Ext.Ajax.request({
43926  *         url: 'gen-invoice-grid.php',
43927  *         // send additional parameters to instruct server script
43928  *         params: {
43929  *             startDate: Ext.getCmp('start-date').getValue(),
43930  *             endDate: Ext.getCmp('end-date').getValue()
43931  *         },
43932  *         // process the response object to add it to the TabPanel:
43933  *         success: function(xhr) {
43934  *             var newComponent = eval(xhr.responseText); // see discussion below
43935  *             myTabPanel.add(newComponent); // add the component to the TabPanel
43936  *             myTabPanel.setActiveTab(newComponent);
43937  *         },
43938  *         failure: function() {
43939  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
43940  *         }
43941  *     });
43942  *
43943  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
43944  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
43945  *
43946  *     {
43947  *         "xtype": 'grid',
43948  *         "title": 'Invoice Report',
43949  *         "store": {
43950  *             "model": 'Invoice',
43951  *             "proxy": {
43952  *                 "type": 'ajax',
43953  *                 "url": 'get-invoice-data.php',
43954  *                 "reader": {
43955  *                     "type": 'json'
43956  *                     "record": 'transaction',
43957  *                     "idProperty": 'id',
43958  *                     "totalRecords": 'total'
43959  *                 })
43960  *             },
43961  *             "autoLoad": {
43962  *                 "params": {
43963  *                     "startDate": '01/01/2008',
43964  *                     "endDate": '01/31/2008'
43965  *                 }
43966  *             }
43967  *         },
43968  *         "headers": [
43969  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
43970  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
43971  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
43972  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
43973  *         ]
43974  *     }
43975  *
43976  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
43977  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
43978  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
43979  *
43980  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
43981  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
43982  * into the code since these are all known on the server.
43983  */
43984 Ext.define('Ext.container.Container', {
43985     extend: 'Ext.container.AbstractContainer',
43986     alias: 'widget.container',
43987     alternateClassName: 'Ext.Container',
43988
43989     /**
43990      * Return the immediate child Component in which the passed element is located.
43991      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
43992      * @return {Ext.Component} The child item which contains the passed element.
43993      */
43994     getChildByElement: function(el) {
43995         var item,
43996             itemEl,
43997             i = 0,
43998             it = this.items.items,
43999             ln = it.length;
44000
44001         el = Ext.getDom(el);
44002         for (; i < ln; i++) {
44003             item = it[i];
44004             itemEl = item.getEl();
44005             if ((itemEl.dom === el) || itemEl.contains(el)) {
44006                 return item;
44007             }
44008         }
44009         return null;
44010     }
44011 });
44012
44013 /**
44014  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
44015  * the right-justified button container.
44016  *
44017  *     @example
44018  *     Ext.create('Ext.panel.Panel', {
44019  *          title: 'Toolbar Fill Example',
44020  *          width: 300,
44021  *          height: 200,
44022  *          tbar : [
44023  *              'Item 1',
44024  *              { xtype: 'tbfill' },
44025  *              'Item 2'
44026  *          ],
44027  *          renderTo: Ext.getBody()
44028  *      });
44029  */
44030 Ext.define('Ext.toolbar.Fill', {
44031     extend: 'Ext.Component',
44032     alias: 'widget.tbfill',
44033     alternateClassName: 'Ext.Toolbar.Fill',
44034     isFill : true,
44035     flex: 1
44036 });
44037 /**
44038  * @class Ext.toolbar.Item
44039  * @extends Ext.Component
44040  * The base class that other non-interacting Toolbar Item classes should extend in order to
44041  * get some basic common toolbar item functionality.
44042  */
44043 Ext.define('Ext.toolbar.Item', {
44044     extend: 'Ext.Component',
44045     alias: 'widget.tbitem',
44046     alternateClassName: 'Ext.Toolbar.Item',
44047     enable:Ext.emptyFn,
44048     disable:Ext.emptyFn,
44049     focus:Ext.emptyFn
44050     /**
44051      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
44052      */
44053 });
44054 /**
44055  * @class Ext.toolbar.Separator
44056  * @extends Ext.toolbar.Item
44057  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
44058  *
44059  *     @example
44060  *     Ext.create('Ext.panel.Panel', {
44061  *         title: 'Toolbar Seperator Example',
44062  *         width: 300,
44063  *         height: 200,
44064  *         tbar : [
44065  *             'Item 1',
44066  *             { xtype: 'tbseparator' },
44067  *             'Item 2'
44068  *         ],
44069  *         renderTo: Ext.getBody()
44070  *     });
44071  */
44072 Ext.define('Ext.toolbar.Separator', {
44073     extend: 'Ext.toolbar.Item',
44074     alias: 'widget.tbseparator',
44075     alternateClassName: 'Ext.Toolbar.Separator',
44076     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
44077     focusable: false
44078 });
44079 /**
44080  * @class Ext.menu.Manager
44081  * Provides a common registry of all menus on a page.
44082  * @singleton
44083  */
44084 Ext.define('Ext.menu.Manager', {
44085     singleton: true,
44086     requires: [
44087         'Ext.util.MixedCollection',
44088         'Ext.util.KeyMap'
44089     ],
44090     alternateClassName: 'Ext.menu.MenuMgr',
44091
44092     uses: ['Ext.menu.Menu'],
44093
44094     menus: {},
44095     groups: {},
44096     attached: false,
44097     lastShow: new Date(),
44098
44099     init: function() {
44100         var me = this;
44101         
44102         me.active = Ext.create('Ext.util.MixedCollection');
44103         Ext.getDoc().addKeyListener(27, function() {
44104             if (me.active.length > 0) {
44105                 me.hideAll();
44106             }
44107         }, me);
44108     },
44109
44110     /**
44111      * Hides all menus that are currently visible
44112      * @return {Boolean} success True if any active menus were hidden.
44113      */
44114     hideAll: function() {
44115         var active = this.active,
44116             c;
44117         if (active && active.length > 0) {
44118             c = active.clone();
44119             c.each(function(m) {
44120                 m.hide();
44121             });
44122             return true;
44123         }
44124         return false;
44125     },
44126
44127     onHide: function(m) {
44128         var me = this,
44129             active = me.active;
44130         active.remove(m);
44131         if (active.length < 1) {
44132             Ext.getDoc().un('mousedown', me.onMouseDown, me);
44133             me.attached = false;
44134         }
44135     },
44136
44137     onShow: function(m) {
44138         var me = this,
44139             active   = me.active,
44140             last     = active.last(),
44141             attached = me.attached,
44142             menuEl   = m.getEl(),
44143             zIndex;
44144
44145         me.lastShow = new Date();
44146         active.add(m);
44147         if (!attached) {
44148             Ext.getDoc().on('mousedown', me.onMouseDown, me);
44149             me.attached = true;
44150         }
44151         m.toFront();
44152     },
44153
44154     onBeforeHide: function(m) {
44155         if (m.activeChild) {
44156             m.activeChild.hide();
44157         }
44158         if (m.autoHideTimer) {
44159             clearTimeout(m.autoHideTimer);
44160             delete m.autoHideTimer;
44161         }
44162     },
44163
44164     onBeforeShow: function(m) {
44165         var active = this.active,
44166             parentMenu = m.parentMenu;
44167             
44168         active.remove(m);
44169         if (!parentMenu && !m.allowOtherMenus) {
44170             this.hideAll();
44171         }
44172         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
44173             parentMenu.activeChild.hide();
44174         }
44175     },
44176
44177     // private
44178     onMouseDown: function(e) {
44179         var me = this,
44180             active = me.active,
44181             lastShow = me.lastShow,
44182             target = e.target;
44183
44184         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
44185             me.hideAll();
44186             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
44187             // fired on the element, so we'll focus it here
44188             if (Ext.isIE && Ext.fly(target).focusable()) {
44189                 target.focus();
44190             }
44191         }
44192     },
44193
44194     // private
44195     register: function(menu) {
44196         var me = this;
44197
44198         if (!me.active) {
44199             me.init();
44200         }
44201
44202         if (menu.floating) {
44203             me.menus[menu.id] = menu;
44204             menu.on({
44205                 beforehide: me.onBeforeHide,
44206                 hide: me.onHide,
44207                 beforeshow: me.onBeforeShow,
44208                 show: me.onShow,
44209                 scope: me
44210             });
44211         }
44212     },
44213
44214     /**
44215      * Returns a {@link Ext.menu.Menu} object
44216      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
44217      * be used to generate and return a new Menu this.
44218      * @return {Ext.menu.Menu} The specified menu, or null if none are found
44219      */
44220     get: function(menu) {
44221         var menus = this.menus;
44222         
44223         if (typeof menu == 'string') { // menu id
44224             if (!menus) {  // not initialized, no menus to return
44225                 return null;
44226             }
44227             return menus[menu];
44228         } else if (menu.isMenu) {  // menu instance
44229             return menu;
44230         } else if (Ext.isArray(menu)) { // array of menu items
44231             return Ext.create('Ext.menu.Menu', {items:menu});
44232         } else { // otherwise, must be a config
44233             return Ext.ComponentManager.create(menu, 'menu');
44234         }
44235     },
44236
44237     // private
44238     unregister: function(menu) {
44239         var me = this,
44240             menus = me.menus,
44241             active = me.active;
44242
44243         delete menus[menu.id];
44244         active.remove(menu);
44245         menu.un({
44246             beforehide: me.onBeforeHide,
44247             hide: me.onHide,
44248             beforeshow: me.onBeforeShow,
44249             show: me.onShow,
44250             scope: me
44251         });
44252     },
44253
44254     // private
44255     registerCheckable: function(menuItem) {
44256         var groups  = this.groups,
44257             groupId = menuItem.group;
44258
44259         if (groupId) {
44260             if (!groups[groupId]) {
44261                 groups[groupId] = [];
44262             }
44263
44264             groups[groupId].push(menuItem);
44265         }
44266     },
44267
44268     // private
44269     unregisterCheckable: function(menuItem) {
44270         var groups  = this.groups,
44271             groupId = menuItem.group;
44272
44273         if (groupId) {
44274             Ext.Array.remove(groups[groupId], menuItem);
44275         }
44276     },
44277
44278     onCheckChange: function(menuItem, state) {
44279         var groups  = this.groups,
44280             groupId = menuItem.group,
44281             i       = 0,
44282             group, ln, curr;
44283
44284         if (groupId && state) {
44285             group = groups[groupId];
44286             ln = group.length;
44287             for (; i < ln; i++) {
44288                 curr = group[i];
44289                 if (curr != menuItem) {
44290                     curr.setChecked(false);
44291                 }
44292             }
44293         }
44294     }
44295 });
44296 /**
44297  * Component layout for buttons
44298  * @class Ext.layout.component.Button
44299  * @extends Ext.layout.component.Component
44300  * @private
44301  */
44302 Ext.define('Ext.layout.component.Button', {
44303
44304     /* Begin Definitions */
44305
44306     alias: ['layout.button'],
44307
44308     extend: 'Ext.layout.component.Component',
44309
44310     /* End Definitions */
44311
44312     type: 'button',
44313
44314     cellClsRE: /-btn-(tl|br)\b/,
44315     htmlRE: /<.*>/,
44316
44317     beforeLayout: function() {
44318         return this.callParent(arguments) || this.lastText !== this.owner.text;
44319     },
44320
44321     /**
44322      * Set the dimensions of the inner &lt;button&gt; element to match the
44323      * component dimensions.
44324      */
44325     onLayout: function(width, height) {
44326         var me = this,
44327             isNum = Ext.isNumber,
44328             owner = me.owner,
44329             ownerEl = owner.el,
44330             btnEl = owner.btnEl,
44331             btnInnerEl = owner.btnInnerEl,
44332             btnIconEl = owner.btnIconEl,
44333             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
44334             minWidth = owner.minWidth,
44335             maxWidth = owner.maxWidth,
44336             ownerWidth, btnFrameWidth, metrics;
44337
44338         me.getTargetInfo();
44339         me.callParent(arguments);
44340
44341         btnInnerEl.unclip();
44342         me.setTargetSize(width, height);
44343
44344         if (!isNum(width)) {
44345             // In IE7 strict mode button elements with width:auto get strange extra side margins within
44346             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
44347             // the size of the text and set the width to match.
44348             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
44349                 btnFrameWidth = me.btnFrameWidth;
44350                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
44351                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
44352                 btnEl.setWidth(metrics.width + btnFrameWidth);
44353                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
44354
44355                 if (sizeIconEl) {
44356                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
44357                 }
44358             } else {
44359                 // Remove any previous fixed widths
44360                 ownerEl.setWidth(null);
44361                 btnEl.setWidth(null);
44362                 btnInnerEl.setWidth(null);
44363                 btnIconEl.setWidth(null);
44364             }
44365
44366             // Handle maxWidth/minWidth config
44367             if (minWidth || maxWidth) {
44368                 ownerWidth = ownerEl.getWidth();
44369                 if (minWidth && (ownerWidth < minWidth)) {
44370                     me.setTargetSize(minWidth, height);
44371                 }
44372                 else if (maxWidth && (ownerWidth > maxWidth)) {
44373                     btnInnerEl.clip();
44374                     me.setTargetSize(maxWidth, height);
44375                 }
44376             }
44377         }
44378
44379         this.lastText = owner.text;
44380     },
44381
44382     setTargetSize: function(width, height) {
44383         var me = this,
44384             owner = me.owner,
44385             isNum = Ext.isNumber,
44386             btnInnerEl = owner.btnInnerEl,
44387             btnWidth = (isNum(width) ? width - me.adjWidth : width),
44388             btnHeight = (isNum(height) ? height - me.adjHeight : height),
44389             btnFrameHeight = me.btnFrameHeight,
44390             text = owner.getText(),
44391             textHeight;
44392
44393         me.callParent(arguments);
44394         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
44395         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44396         if (btnHeight >= 0) {
44397             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
44398         }
44399
44400         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
44401         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
44402         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
44403         // vertically within the button's height. This is more expensive than the basic line-height approach so
44404         // we only do it if the text contains markup.
44405         if (text && this.htmlRE.test(text)) {
44406             btnInnerEl.setStyle('line-height', 'normal');
44407             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
44408             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
44409             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44410         }
44411     },
44412
44413     getTargetInfo: function() {
44414         var me = this,
44415             owner = me.owner,
44416             ownerEl = owner.el,
44417             frameSize = me.frameSize,
44418             frameBody = owner.frameBody,
44419             btnWrap = owner.btnWrap,
44420             innerEl = owner.btnInnerEl;
44421
44422         if (!('adjWidth' in me)) {
44423             Ext.apply(me, {
44424                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
44425                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
44426                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
44427                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
44428                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
44429                 btnFrameWidth: innerEl.getFrameWidth('lr'),
44430                 btnFrameHeight: innerEl.getFrameWidth('tb'),
44431                 btnFrameTop: innerEl.getFrameWidth('t')
44432             });
44433         }
44434
44435         return me.callParent();
44436     }
44437 });
44438 /**
44439  * @docauthor Robert Dougan <rob@sencha.com>
44440  *
44441  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
44442  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
44443  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
44444  * a user clicks the button, or use {@link #listeners listeners} for other events such as
44445  * {@link #mouseover mouseover}. Example usage:
44446  *
44447  *     @example
44448  *     Ext.create('Ext.Button', {
44449  *         text: 'Click me',
44450  *         renderTo: Ext.getBody(),
44451  *         handler: function() {
44452  *             alert('You clicked the button!')
44453  *         }
44454  *     });
44455  *
44456  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
44457  * method.  Example usage:
44458  *
44459  *     @example
44460  *     Ext.create('Ext.Button', {
44461  *         text    : 'Dynamic Handler Button',
44462  *         renderTo: Ext.getBody(),
44463  *         handler : function() {
44464  *             // this button will spit out a different number every time you click it.
44465  *             // so firstly we must check if that number is already set:
44466  *             if (this.clickCount) {
44467  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
44468  *                 this.clickCount++;
44469  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
44470  *             } else {
44471  *                 // if the clickCount property is not set, we will set it and alert the user
44472  *                 this.clickCount = 1;
44473  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
44474  *             }
44475  *         }
44476  *     });
44477  *
44478  * A button within a container:
44479  *
44480  *     @example
44481  *     Ext.create('Ext.Container', {
44482  *         renderTo: Ext.getBody(),
44483  *         items   : [
44484  *             {
44485  *                 xtype: 'button',
44486  *                 text : 'My Button'
44487  *             }
44488  *         ]
44489  *     });
44490  *
44491  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
44492  *
44493  * - `'small'`
44494  * - `'medium'`
44495  * - `'large'`
44496  *
44497  * Example usage:
44498  *
44499  *     @example
44500  *     Ext.create('Ext.Button', {
44501  *         renderTo: document.body,
44502  *         text    : 'Click me',
44503  *         scale   : 'large'
44504  *     });
44505  *
44506  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
44507  * Example usage:
44508  *
44509  *     @example
44510  *     Ext.create('Ext.Button', {
44511  *         renderTo: Ext.getBody(),
44512  *         text: 'Click Me',
44513  *         enableToggle: true
44514  *     });
44515  *
44516  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
44517  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
44518  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
44519  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
44520  * on button.  Example usage:
44521  *
44522  *     @example
44523  *     Ext.create('Ext.Button', {
44524  *         text      : 'Menu button',
44525  *         renderTo  : Ext.getBody(),
44526  *         arrowAlign: 'bottom',
44527  *         menu      : [
44528  *             {text: 'Item 1'},
44529  *             {text: 'Item 2'},
44530  *             {text: 'Item 3'},
44531  *             {text: 'Item 4'}
44532  *         ]
44533  *     });
44534  *
44535  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
44536  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
44537  *
44538  * - `click`
44539  * - `toggle`
44540  * - `mouseover`
44541  * - `mouseout`
44542  * - `mouseshow`
44543  * - `menuhide`
44544  * - `menutriggerover`
44545  * - `menutriggerout`
44546  *
44547  * Example usage:
44548  *
44549  *     @example
44550  *     Ext.create('Ext.Button', {
44551  *         text     : 'Button',
44552  *         renderTo : Ext.getBody(),
44553  *         listeners: {
44554  *             click: function() {
44555  *                 // this == the button, as we are in the local scope
44556  *                 this.setText('I was clicked!');
44557  *             },
44558  *             mouseover: function() {
44559  *                 // set a new config which says we moused over, if not already set
44560  *                 if (!this.mousedOver) {
44561  *                     this.mousedOver = true;
44562  *                     alert('You moused over a button!\n\nI wont do this again.');
44563  *                 }
44564  *             }
44565  *         }
44566  *     });
44567  */
44568 Ext.define('Ext.button.Button', {
44569
44570     /* Begin Definitions */
44571     alias: 'widget.button',
44572     extend: 'Ext.Component',
44573
44574     requires: [
44575         'Ext.menu.Manager',
44576         'Ext.util.ClickRepeater',
44577         'Ext.layout.component.Button',
44578         'Ext.util.TextMetrics',
44579         'Ext.util.KeyMap'
44580     ],
44581
44582     alternateClassName: 'Ext.Button',
44583     /* End Definitions */
44584
44585     isButton: true,
44586     componentLayout: 'button',
44587
44588     /**
44589      * @property {Boolean} hidden
44590      * True if this button is hidden. Read-only.
44591      */
44592     hidden: false,
44593
44594     /**
44595      * @property {Boolean} disabled
44596      * True if this button is disabled. Read-only.
44597      */
44598     disabled: false,
44599
44600     /**
44601      * @property {Boolean} pressed
44602      * True if this button is pressed (only if enableToggle = true). Read-only.
44603      */
44604     pressed: false,
44605
44606     /**
44607      * @cfg {String} text
44608      * The button text to be used as innerHTML (html tags are accepted).
44609      */
44610
44611     /**
44612      * @cfg {String} icon
44613      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
44614      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
44615      */
44616
44617     /**
44618      * @cfg {Function} handler
44619      * A function called when the button is clicked (can be used instead of click event).
44620      * @cfg {Ext.button.Button} handler.button This button.
44621      * @cfg {Ext.EventObject} handler.e The click event.
44622      */
44623
44624     /**
44625      * @cfg {Number} minWidth
44626      * The minimum width for this button (used to give a set of buttons a common width).
44627      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
44628      */
44629
44630     /**
44631      * @cfg {String/Object} tooltip
44632      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
44633      * QuickTips config object.
44634      */
44635
44636     /**
44637      * @cfg {Boolean} [hidden=false]
44638      * True to start hidden.
44639      */
44640
44641     /**
44642      * @cfg {Boolean} [disabled=true]
44643      * True to start disabled.
44644      */
44645
44646     /**
44647      * @cfg {Boolean} [pressed=false]
44648      * True to start pressed (only if enableToggle = true)
44649      */
44650
44651     /**
44652      * @cfg {String} toggleGroup
44653      * The group this toggle button is a member of (only 1 per group can be pressed)
44654      */
44655
44656     /**
44657      * @cfg {Boolean/Object} [repeat=false]
44658      * True to repeat fire the click event while the mouse is down. This can also be a
44659      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
44660      */
44661
44662     /**
44663      * @cfg {Number} tabIndex
44664      * Set a DOM tabIndex for this button.
44665      */
44666
44667     /**
44668      * @cfg {Boolean} [allowDepress=true]
44669      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
44670      */
44671
44672     /**
44673      * @cfg {Boolean} [enableToggle=false]
44674      * True to enable pressed/not pressed toggling.
44675      */
44676     enableToggle: false,
44677
44678     /**
44679      * @cfg {Function} toggleHandler
44680      * Function called when a Button with {@link #enableToggle} set to true is clicked.
44681      * @cfg {Ext.button.Button} toggleHandler.button This button.
44682      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
44683      */
44684
44685     /**
44686      * @cfg {Ext.menu.Menu/String/Object} menu
44687      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
44688      */
44689
44690     /**
44691      * @cfg {String} menuAlign
44692      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
44693      */
44694     menuAlign: 'tl-bl?',
44695
44696     /**
44697      * @cfg {String} textAlign
44698      * The text alignment for this button (center, left, right).
44699      */
44700     textAlign: 'center',
44701
44702     /**
44703      * @cfg {String} overflowText
44704      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
44705      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
44706      */
44707
44708     /**
44709      * @cfg {String} iconCls
44710      * A css class which sets a background image to be used as the icon for this button.
44711      */
44712
44713     /**
44714      * @cfg {String} type
44715      * The type of `<input>` to create: submit, reset or button.
44716      */
44717     type: 'button',
44718
44719     /**
44720      * @cfg {String} clickEvent
44721      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
44722      */
44723     clickEvent: 'click',
44724
44725     /**
44726      * @cfg {Boolean} preventDefault
44727      * True to prevent the default action when the {@link #clickEvent} is processed.
44728      */
44729     preventDefault: true,
44730
44731     /**
44732      * @cfg {Boolean} handleMouseEvents
44733      * False to disable visual cues on mouseover, mouseout and mousedown.
44734      */
44735     handleMouseEvents: true,
44736
44737     /**
44738      * @cfg {String} tooltipType
44739      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
44740      */
44741     tooltipType: 'qtip',
44742
44743     /**
44744      * @cfg {String} [baseCls='x-btn']
44745      * The base CSS class to add to all buttons.
44746      */
44747     baseCls: Ext.baseCSSPrefix + 'btn',
44748
44749     /**
44750      * @cfg {String} pressedCls
44751      * The CSS class to add to a button when it is in the pressed state.
44752      */
44753     pressedCls: 'pressed',
44754
44755     /**
44756      * @cfg {String} overCls
44757      * The CSS class to add to a button when it is in the over (hovered) state.
44758      */
44759     overCls: 'over',
44760
44761     /**
44762      * @cfg {String} focusCls
44763      * The CSS class to add to a button when it is in the focussed state.
44764      */
44765     focusCls: 'focus',
44766
44767     /**
44768      * @cfg {String} menuActiveCls
44769      * The CSS class to add to a button when it's menu is active.
44770      */
44771     menuActiveCls: 'menu-active',
44772
44773     /**
44774      * @cfg {String} href
44775      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
44776      *
44777      *     handler: function() { window.location = "http://www.sencha.com" }
44778      */
44779
44780     /**
44781      * @cfg {Object} baseParams
44782      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
44783      */
44784
44785     /**
44786      * @cfg {Object} params
44787      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
44788      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
44789      */
44790
44791     ariaRole: 'button',
44792
44793     // inherited
44794     renderTpl:
44795         '<em id="{id}-btnWrap" class="{splitCls}">' +
44796             '<tpl if="href">' +
44797                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
44798                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
44799                         '{text}' +
44800                     '</span>' +
44801                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
44802                 '</a>' +
44803             '</tpl>' +
44804             '<tpl if="!href">' +
44805                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
44806                     // the autocomplete="off" is required to prevent Firefox from remembering
44807                     // the button's disabled state between page reloads.
44808                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
44809                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
44810                         '{text}' +
44811                     '</span>' +
44812                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
44813                 '</button>' +
44814             '</tpl>' +
44815         '</em>' ,
44816
44817     /**
44818      * @cfg {String} scale
44819      * The size of the Button. Three values are allowed:
44820      *
44821      * - 'small' - Results in the button element being 16px high.
44822      * - 'medium' - Results in the button element being 24px high.
44823      * - 'large' - Results in the button element being 32px high.
44824      */
44825     scale: 'small',
44826
44827     /**
44828      * @private
44829      * An array of allowed scales.
44830      */
44831     allowedScales: ['small', 'medium', 'large'],
44832
44833     /**
44834      * @cfg {Object} scope
44835      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
44836      * Defaults to this Button.
44837      */
44838
44839     /**
44840      * @cfg {String} iconAlign
44841      * The side of the Button box to render the icon. Four values are allowed:
44842      *
44843      * - 'top'
44844      * - 'right'
44845      * - 'bottom'
44846      * - 'left'
44847      */
44848     iconAlign: 'left',
44849
44850     /**
44851      * @cfg {String} arrowAlign
44852      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
44853      * values are allowed:
44854      *
44855      * - 'right'
44856      * - 'bottom'
44857      */
44858     arrowAlign: 'right',
44859
44860     /**
44861      * @cfg {String} arrowCls
44862      * The className used for the inner arrow element if the button has a menu.
44863      */
44864     arrowCls: 'arrow',
44865
44866     /**
44867      * @property {Ext.Template} template
44868      * A {@link Ext.Template Template} used to create the Button's DOM structure.
44869      *
44870      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
44871      * conjunction with an implementation of {@link #getTemplateArgs}.
44872      */
44873
44874     /**
44875      * @cfg {String} cls
44876      * A CSS class string to apply to the button's main element.
44877      */
44878
44879     /**
44880      * @property {Ext.menu.Menu} menu
44881      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
44882      * option.
44883      */
44884
44885     /**
44886      * @cfg {Boolean} autoWidth
44887      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
44888      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
44889      * from doing this automatic sizing.
44890      */
44891
44892     maskOnDisable: false,
44893
44894     // inherit docs
44895     initComponent: function() {
44896         var me = this;
44897         me.callParent(arguments);
44898
44899         me.addEvents(
44900             /**
44901              * @event click
44902              * Fires when this button is clicked
44903              * @param {Ext.button.Button} this
44904              * @param {Event} e The click event
44905              */
44906             'click',
44907
44908             /**
44909              * @event toggle
44910              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
44911              * @param {Ext.button.Button} this
44912              * @param {Boolean} pressed
44913              */
44914             'toggle',
44915
44916             /**
44917              * @event mouseover
44918              * Fires when the mouse hovers over the button
44919              * @param {Ext.button.Button} this
44920              * @param {Event} e The event object
44921              */
44922             'mouseover',
44923
44924             /**
44925              * @event mouseout
44926              * Fires when the mouse exits the button
44927              * @param {Ext.button.Button} this
44928              * @param {Event} e The event object
44929              */
44930             'mouseout',
44931
44932             /**
44933              * @event menushow
44934              * If this button has a menu, this event fires when it is shown
44935              * @param {Ext.button.Button} this
44936              * @param {Ext.menu.Menu} menu
44937              */
44938             'menushow',
44939
44940             /**
44941              * @event menuhide
44942              * If this button has a menu, this event fires when it is hidden
44943              * @param {Ext.button.Button} this
44944              * @param {Ext.menu.Menu} menu
44945              */
44946             'menuhide',
44947
44948             /**
44949              * @event menutriggerover
44950              * If this button has a menu, this event fires when the mouse enters the menu triggering element
44951              * @param {Ext.button.Button} this
44952              * @param {Ext.menu.Menu} menu
44953              * @param {Event} e
44954              */
44955             'menutriggerover',
44956
44957             /**
44958              * @event menutriggerout
44959              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
44960              * @param {Ext.button.Button} this
44961              * @param {Ext.menu.Menu} menu
44962              * @param {Event} e
44963              */
44964             'menutriggerout'
44965         );
44966
44967         if (me.menu) {
44968             // Flag that we'll have a splitCls
44969             me.split = true;
44970
44971             // retrieve menu by id or instantiate instance if needed
44972             me.menu = Ext.menu.Manager.get(me.menu);
44973             me.menu.ownerCt = me;
44974         }
44975
44976         // Accept url as a synonym for href
44977         if (me.url) {
44978             me.href = me.url;
44979         }
44980
44981         // preventDefault defaults to false for links
44982         if (me.href && !me.hasOwnProperty('preventDefault')) {
44983             me.preventDefault = false;
44984         }
44985
44986         if (Ext.isString(me.toggleGroup)) {
44987             me.enableToggle = true;
44988         }
44989
44990     },
44991
44992     // private
44993     initAria: function() {
44994         this.callParent();
44995         var actionEl = this.getActionEl();
44996         if (this.menu) {
44997             actionEl.dom.setAttribute('aria-haspopup', true);
44998         }
44999     },
45000
45001     // inherit docs
45002     getActionEl: function() {
45003         return this.btnEl;
45004     },
45005
45006     // inherit docs
45007     getFocusEl: function() {
45008         return this.btnEl;
45009     },
45010
45011     // private
45012     setButtonCls: function() {
45013         var me = this,
45014             cls = [],
45015             btnIconEl = me.btnIconEl,
45016             hide = 'x-hide-display';
45017
45018         if (me.useSetClass) {
45019             if (!Ext.isEmpty(me.oldCls)) {
45020                 me.removeClsWithUI(me.oldCls);
45021                 me.removeClsWithUI(me.pressedCls);
45022             }
45023
45024             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
45025             if (me.iconCls || me.icon) {
45026                 if (me.text) {
45027                     cls.push('icon-text-' + me.iconAlign);
45028                 } else {
45029                     cls.push('icon');
45030                 }
45031                 if (btnIconEl) {
45032                     btnIconEl.removeCls(hide);
45033                 }
45034             } else {
45035                 if (me.text) {
45036                     cls.push('noicon');
45037                 }
45038                 if (btnIconEl) {
45039                     btnIconEl.addCls(hide);
45040                 }
45041             }
45042
45043             me.oldCls = cls;
45044             me.addClsWithUI(cls);
45045             me.addClsWithUI(me.pressed ? me.pressedCls : null);
45046         }
45047     },
45048
45049     // private
45050     onRender: function(ct, position) {
45051         // classNames for the button
45052         var me = this,
45053             repeater, btn;
45054
45055         // Apply the renderData to the template args
45056         Ext.applyIf(me.renderData, me.getTemplateArgs());
45057
45058         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
45059
45060         if (me.scale) {
45061             me.ui = me.ui + '-' + me.scale;
45062         }
45063
45064         // Render internal structure
45065         me.callParent(arguments);
45066
45067         // If it is a split button + has a toolip for the arrow
45068         if (me.split && me.arrowTooltip) {
45069             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
45070         }
45071
45072         // Add listeners to the focus and blur events on the element
45073         me.mon(me.btnEl, {
45074             scope: me,
45075             focus: me.onFocus,
45076             blur : me.onBlur
45077         });
45078
45079         // Set btn as a local variable for easy access
45080         btn = me.el;
45081
45082         if (me.icon) {
45083             me.setIcon(me.icon);
45084         }
45085
45086         if (me.iconCls) {
45087             me.setIconCls(me.iconCls);
45088         }
45089
45090         if (me.tooltip) {
45091             me.setTooltip(me.tooltip, true);
45092         }
45093
45094         if (me.textAlign) {
45095             me.setTextAlign(me.textAlign);
45096         }
45097
45098         // Add the mouse events to the button
45099         if (me.handleMouseEvents) {
45100             me.mon(btn, {
45101                 scope: me,
45102                 mouseover: me.onMouseOver,
45103                 mouseout: me.onMouseOut,
45104                 mousedown: me.onMouseDown
45105             });
45106
45107             if (me.split) {
45108                 me.mon(btn, {
45109                     mousemove: me.onMouseMove,
45110                     scope: me
45111                 });
45112             }
45113         }
45114
45115         // Check if the button has a menu
45116         if (me.menu) {
45117             me.mon(me.menu, {
45118                 scope: me,
45119                 show: me.onMenuShow,
45120                 hide: me.onMenuHide
45121             });
45122
45123             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
45124                 key: Ext.EventObject.DOWN,
45125                 handler: me.onDownKey,
45126                 scope: me
45127             });
45128         }
45129
45130         // Check if it is a repeat button
45131         if (me.repeat) {
45132             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
45133             me.mon(repeater, 'click', me.onRepeatClick, me);
45134         } else {
45135             me.mon(btn, me.clickEvent, me.onClick, me);
45136         }
45137
45138         // Register the button in the toggle manager
45139         Ext.ButtonToggleManager.register(me);
45140     },
45141
45142     /**
45143      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
45144      * create this Button's DOM structure.
45145      *
45146      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
45147      * their own implementation of this method.
45148      *
45149      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
45150      * {@link #template} returns an Object containing the following properties:
45151      * @return {String} return.type The `<button>`'s {@link #type}
45152      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
45153      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
45154      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
45155      * button's scale and icon alignment.
45156      * @return {String} return.text The {@link #text} to display ion the Button.
45157      * @return {Number} return.tabIndex The tab index within the input flow.
45158      */
45159     getTemplateArgs: function() {
45160         var me = this,
45161             persistentPadding = me.getPersistentBtnPadding(),
45162             innerSpanStyle = '';
45163
45164         // Create negative margin offsets to counteract persistent button padding if needed
45165         if (Math.max.apply(Math, persistentPadding) > 0) {
45166             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
45167                 return -pad + 'px';
45168             }).join(' ');
45169         }
45170
45171         return {
45172             href     : me.getHref(),
45173             target   : me.target || '_blank',
45174             type     : me.type,
45175             splitCls : me.getSplitCls(),
45176             cls      : me.cls,
45177             iconCls  : me.iconCls || '',
45178             text     : me.text || '&#160;',
45179             tabIndex : me.tabIndex,
45180             innerSpanStyle: innerSpanStyle
45181         };
45182     },
45183
45184     /**
45185      * @private
45186      * If there is a configured href for this Button, returns the href with parameters appended.
45187      * @returns The href string with parameters appended.
45188      */
45189     getHref: function() {
45190         var me = this,
45191             params = Ext.apply({}, me.baseParams);
45192
45193         // write baseParams first, then write any params
45194         params = Ext.apply(params, me.params);
45195         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
45196     },
45197
45198     /**
45199      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
45200      *
45201      * **Only valid if the Button was originally configured with a {@link #href}**
45202      *
45203      * @param {Object} params Parameters to use in the href URL.
45204      */
45205     setParams: function(params) {
45206         this.params = params;
45207         this.btnEl.dom.href = this.getHref();
45208     },
45209
45210     getSplitCls: function() {
45211         var me = this;
45212         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
45213     },
45214
45215     // private
45216     afterRender: function() {
45217         var me = this;
45218         me.useSetClass = true;
45219         me.setButtonCls();
45220         me.doc = Ext.getDoc();
45221         this.callParent(arguments);
45222     },
45223
45224     /**
45225      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
45226      * value of the {@link #iconCls} config internally.
45227      * @param {String} cls The CSS class providing the icon image
45228      * @return {Ext.button.Button} this
45229      */
45230     setIconCls: function(cls) {
45231         var me = this,
45232             btnIconEl = me.btnIconEl,
45233             oldCls = me.iconCls;
45234             
45235         me.iconCls = cls;
45236         if (btnIconEl) {
45237             // Remove the previous iconCls from the button
45238             btnIconEl.removeCls(oldCls);
45239             btnIconEl.addCls(cls || '');
45240             me.setButtonCls();
45241         }
45242         return me;
45243     },
45244
45245     /**
45246      * Sets the tooltip for this Button.
45247      *
45248      * @param {String/Object} tooltip This may be:
45249      *
45250      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
45251      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
45252      *
45253      * @return {Ext.button.Button} this
45254      */
45255     setTooltip: function(tooltip, initial) {
45256         var me = this;
45257
45258         if (me.rendered) {
45259             if (!initial) {
45260                 me.clearTip();
45261             }
45262             if (Ext.isObject(tooltip)) {
45263                 Ext.tip.QuickTipManager.register(Ext.apply({
45264                     target: me.btnEl.id
45265                 },
45266                 tooltip));
45267                 me.tooltip = tooltip;
45268             } else {
45269                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
45270             }
45271         } else {
45272             me.tooltip = tooltip;
45273         }
45274         return me;
45275     },
45276
45277     /**
45278      * Sets the text alignment for this button.
45279      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
45280      */
45281     setTextAlign: function(align) {
45282         var me = this,
45283             btnEl = me.btnEl;
45284
45285         if (btnEl) {
45286             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
45287             btnEl.addCls(me.baseCls + '-' + align);
45288         }
45289         me.textAlign = align;
45290         return me;
45291     },
45292
45293     getTipAttr: function(){
45294         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
45295     },
45296
45297     // private
45298     getRefItems: function(deep){
45299         var menu = this.menu,
45300             items;
45301         
45302         if (menu) {
45303             items = menu.getRefItems(deep);
45304             items.unshift(menu);
45305         }
45306         return items || [];
45307     },
45308
45309     // private
45310     clearTip: function() {
45311         if (Ext.isObject(this.tooltip)) {
45312             Ext.tip.QuickTipManager.unregister(this.btnEl);
45313         }
45314     },
45315
45316     // private
45317     beforeDestroy: function() {
45318         var me = this;
45319         if (me.rendered) {
45320             me.clearTip();
45321         }
45322         if (me.menu && me.destroyMenu !== false) {
45323             Ext.destroy(me.menu);
45324         }
45325         Ext.destroy(me.btnInnerEl, me.repeater);
45326         me.callParent();
45327     },
45328
45329     // private
45330     onDestroy: function() {
45331         var me = this;
45332         if (me.rendered) {
45333             me.doc.un('mouseover', me.monitorMouseOver, me);
45334             me.doc.un('mouseup', me.onMouseUp, me);
45335             delete me.doc;
45336             Ext.ButtonToggleManager.unregister(me);
45337
45338             Ext.destroy(me.keyMap);
45339             delete me.keyMap;
45340         }
45341         me.callParent();
45342     },
45343
45344     /**
45345      * Assigns this Button's click handler
45346      * @param {Function} handler The function to call when the button is clicked
45347      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
45348      * Defaults to this Button.
45349      * @return {Ext.button.Button} this
45350      */
45351     setHandler: function(handler, scope) {
45352         this.handler = handler;
45353         this.scope = scope;
45354         return this;
45355     },
45356
45357     /**
45358      * Sets this Button's text
45359      * @param {String} text The button text
45360      * @return {Ext.button.Button} this
45361      */
45362     setText: function(text) {
45363         var me = this;
45364         me.text = text;
45365         if (me.el) {
45366             me.btnInnerEl.update(text || '&#160;');
45367             me.setButtonCls();
45368         }
45369         me.doComponentLayout();
45370         return me;
45371     },
45372
45373     /**
45374      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
45375      * config internally.
45376      * @param {String} icon The path to an image to display in the button
45377      * @return {Ext.button.Button} this
45378      */
45379     setIcon: function(icon) {
45380         var me = this,
45381             iconEl = me.btnIconEl;
45382             
45383         me.icon = icon;
45384         if (iconEl) {
45385             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
45386             me.setButtonCls();
45387         }
45388         return me;
45389     },
45390
45391     /**
45392      * Gets the text for this Button
45393      * @return {String} The button text
45394      */
45395     getText: function() {
45396         return this.text;
45397     },
45398
45399     /**
45400      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
45401      * @param {Boolean} [state] Force a particular state
45402      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
45403      * @return {Ext.button.Button} this
45404      */
45405     toggle: function(state, suppressEvent) {
45406         var me = this;
45407         state = state === undefined ? !me.pressed : !!state;
45408         if (state !== me.pressed) {
45409             if (me.rendered) {
45410                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
45411             }
45412             me.btnEl.dom.setAttribute('aria-pressed', state);
45413             me.pressed = state;
45414             if (!suppressEvent) {
45415                 me.fireEvent('toggle', me, state);
45416                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
45417             }
45418         }
45419         return me;
45420     },
45421     
45422     maybeShowMenu: function(){
45423         var me = this;
45424         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
45425             me.showMenu();
45426         }
45427     },
45428
45429     /**
45430      * Shows this button's menu (if it has one)
45431      */
45432     showMenu: function() {
45433         var me = this;
45434         if (me.rendered && me.menu) {
45435             if (me.tooltip && me.getTipAttr() != 'title') {
45436                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
45437             }
45438             if (me.menu.isVisible()) {
45439                 me.menu.hide();
45440             }
45441
45442             me.menu.showBy(me.el, me.menuAlign);
45443         }
45444         return me;
45445     },
45446
45447     /**
45448      * Hides this button's menu (if it has one)
45449      */
45450     hideMenu: function() {
45451         if (this.hasVisibleMenu()) {
45452             this.menu.hide();
45453         }
45454         return this;
45455     },
45456
45457     /**
45458      * Returns true if the button has a menu and it is visible
45459      * @return {Boolean}
45460      */
45461     hasVisibleMenu: function() {
45462         var menu = this.menu;
45463         return menu && menu.rendered && menu.isVisible();
45464     },
45465
45466     // private
45467     onRepeatClick: function(repeat, e) {
45468         this.onClick(e);
45469     },
45470
45471     // private
45472     onClick: function(e) {
45473         var me = this;
45474         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
45475             e.preventDefault();
45476         }
45477         if (e.button !== 0) {
45478             return;
45479         }
45480         if (!me.disabled) {
45481             me.doToggle();
45482             me.maybeShowMenu();
45483             me.fireHandler(e);
45484         }
45485     },
45486     
45487     fireHandler: function(e){
45488         var me = this,
45489             handler = me.handler;
45490             
45491         me.fireEvent('click', me, e);
45492         if (handler) {
45493             handler.call(me.scope || me, me, e);
45494         }
45495         me.onBlur();
45496     },
45497     
45498     doToggle: function(){
45499         var me = this;
45500         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
45501             me.toggle();
45502         }
45503     },
45504
45505     /**
45506      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
45507      * The targets are interrogated to see what is being entered from where.
45508      * @param e
45509      */
45510     onMouseOver: function(e) {
45511         var me = this;
45512         if (!me.disabled && !e.within(me.el, true, true)) {
45513             me.onMouseEnter(e);
45514         }
45515     },
45516
45517     /**
45518      * @private
45519      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
45520      * or the mouse leaves the encapsulating element.
45521      * The targets are interrogated to see what is being exited to where.
45522      * @param e
45523      */
45524     onMouseOut: function(e) {
45525         var me = this;
45526         if (!e.within(me.el, true, true)) {
45527             if (me.overMenuTrigger) {
45528                 me.onMenuTriggerOut(e);
45529             }
45530             me.onMouseLeave(e);
45531         }
45532     },
45533
45534     /**
45535      * @private
45536      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
45537      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
45538      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
45539      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
45540      * events when needed. In the future we should consider making the trigger a separate element that
45541      * is absolutely positioned and sized over the trigger area.
45542      */
45543     onMouseMove: function(e) {
45544         var me = this,
45545             el = me.el,
45546             over = me.overMenuTrigger,
45547             overlap, btnSize;
45548
45549         if (me.split) {
45550             if (me.arrowAlign === 'right') {
45551                 overlap = e.getX() - el.getX();
45552                 btnSize = el.getWidth();
45553             } else {
45554                 overlap = e.getY() - el.getY();
45555                 btnSize = el.getHeight();
45556             }
45557
45558             if (overlap > (btnSize - me.getTriggerSize())) {
45559                 if (!over) {
45560                     me.onMenuTriggerOver(e);
45561                 }
45562             } else {
45563                 if (over) {
45564                     me.onMenuTriggerOut(e);
45565                 }
45566             }
45567         }
45568     },
45569
45570     /**
45571      * @private
45572      * Measures the size of the trigger area for menu and split buttons. Will be a width for
45573      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
45574      */
45575     getTriggerSize: function() {
45576         var me = this,
45577             size = me.triggerSize,
45578             side, sideFirstLetter, undef;
45579
45580         if (size === undef) {
45581             side = me.arrowAlign;
45582             sideFirstLetter = side.charAt(0);
45583             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
45584         }
45585         return size;
45586     },
45587
45588     /**
45589      * @private
45590      * virtual mouseenter handler called when it is detected that the mouseout event
45591      * signified the mouse entering the encapsulating element.
45592      * @param e
45593      */
45594     onMouseEnter: function(e) {
45595         var me = this;
45596         me.addClsWithUI(me.overCls);
45597         me.fireEvent('mouseover', me, e);
45598     },
45599
45600     /**
45601      * @private
45602      * virtual mouseleave handler called when it is detected that the mouseover event
45603      * signified the mouse entering the encapsulating element.
45604      * @param e
45605      */
45606     onMouseLeave: function(e) {
45607         var me = this;
45608         me.removeClsWithUI(me.overCls);
45609         me.fireEvent('mouseout', me, e);
45610     },
45611
45612     /**
45613      * @private
45614      * virtual mouseenter handler called when it is detected that the mouseover event
45615      * signified the mouse entering the arrow area of the button - the <em>.
45616      * @param e
45617      */
45618     onMenuTriggerOver: function(e) {
45619         var me = this;
45620         me.overMenuTrigger = true;
45621         me.fireEvent('menutriggerover', me, me.menu, e);
45622     },
45623
45624     /**
45625      * @private
45626      * virtual mouseleave handler called when it is detected that the mouseout event
45627      * signified the mouse leaving the arrow area of the button - the <em>.
45628      * @param e
45629      */
45630     onMenuTriggerOut: function(e) {
45631         var me = this;
45632         delete me.overMenuTrigger;
45633         me.fireEvent('menutriggerout', me, me.menu, e);
45634     },
45635
45636     // inherit docs
45637     enable : function(silent) {
45638         var me = this;
45639
45640         me.callParent(arguments);
45641
45642         me.removeClsWithUI('disabled');
45643
45644         return me;
45645     },
45646
45647     // inherit docs
45648     disable : function(silent) {
45649         var me = this;
45650
45651         me.callParent(arguments);
45652
45653         me.addClsWithUI('disabled');
45654         me.removeClsWithUI(me.overCls);
45655
45656         return me;
45657     },
45658
45659     /**
45660      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
45661      * @param {String} scale The scale to change to.
45662      */
45663     setScale: function(scale) {
45664         var me = this,
45665             ui = me.ui.replace('-' + me.scale, '');
45666
45667         //check if it is an allowed scale
45668         if (!Ext.Array.contains(me.allowedScales, scale)) {
45669             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
45670         }
45671
45672         me.scale = scale;
45673         me.setUI(ui);
45674     },
45675
45676     // inherit docs
45677     setUI: function(ui) {
45678         var me = this;
45679
45680         //we need to append the scale to the UI, if not already done
45681         if (me.scale && !ui.match(me.scale)) {
45682             ui = ui + '-' + me.scale;
45683         }
45684
45685         me.callParent([ui]);
45686
45687         // Set all the state classNames, as they need to include the UI
45688         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
45689     },
45690
45691     // private
45692     onFocus: function(e) {
45693         var me = this;
45694         if (!me.disabled) {
45695             me.addClsWithUI(me.focusCls);
45696         }
45697     },
45698
45699     // private
45700     onBlur: function(e) {
45701         var me = this;
45702         me.removeClsWithUI(me.focusCls);
45703     },
45704
45705     // private
45706     onMouseDown: function(e) {
45707         var me = this;
45708         if (!me.disabled && e.button === 0) {
45709             me.addClsWithUI(me.pressedCls);
45710             me.doc.on('mouseup', me.onMouseUp, me);
45711         }
45712     },
45713     // private
45714     onMouseUp: function(e) {
45715         var me = this;
45716         if (e.button === 0) {
45717             if (!me.pressed) {
45718                 me.removeClsWithUI(me.pressedCls);
45719             }
45720             me.doc.un('mouseup', me.onMouseUp, me);
45721         }
45722     },
45723     // private
45724     onMenuShow: function(e) {
45725         var me = this;
45726         me.ignoreNextClick = 0;
45727         me.addClsWithUI(me.menuActiveCls);
45728         me.fireEvent('menushow', me, me.menu);
45729     },
45730
45731     // private
45732     onMenuHide: function(e) {
45733         var me = this;
45734         me.removeClsWithUI(me.menuActiveCls);
45735         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
45736         me.fireEvent('menuhide', me, me.menu);
45737     },
45738
45739     // private
45740     restoreClick: function() {
45741         this.ignoreNextClick = 0;
45742     },
45743
45744     // private
45745     onDownKey: function() {
45746         var me = this;
45747
45748         if (!me.disabled) {
45749             if (me.menu) {
45750                 me.showMenu();
45751             }
45752         }
45753     },
45754
45755     /**
45756      * @private
45757      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
45758      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
45759      * @return {Number[]} [top, right, bottom, left]
45760      */
45761     getPersistentBtnPadding: function() {
45762         var cls = Ext.button.Button,
45763             padding = cls.persistentPadding,
45764             btn, leftTop, btnEl, btnInnerEl;
45765
45766         if (!padding) {
45767             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
45768
45769             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
45770                 // Create auto-size button offscreen and measure its insides
45771                 btn = Ext.create('Ext.button.Button', {
45772                     renderTo: Ext.getBody(),
45773                     text: 'test',
45774                     style: 'position:absolute;top:-999px;'
45775                 });
45776                 btnEl = btn.btnEl;
45777                 btnInnerEl = btn.btnInnerEl;
45778                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
45779
45780                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
45781                 padding[0] = leftTop[1];
45782                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
45783                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
45784                 padding[3] = leftTop[0];
45785
45786                 btn.destroy();
45787             }
45788         }
45789
45790         return padding;
45791     }
45792
45793 }, function() {
45794     var groups = {};
45795
45796     function toggleGroup(btn, state) {
45797         var g, i, l;
45798         if (state) {
45799             g = groups[btn.toggleGroup];
45800             for (i = 0, l = g.length; i < l; i++) {
45801                 if (g[i] !== btn) {
45802                     g[i].toggle(false);
45803                 }
45804             }
45805         }
45806     }
45807
45808     /**
45809      * Private utility class used by Button
45810      * @hide
45811      */
45812     Ext.ButtonToggleManager = {
45813         register: function(btn) {
45814             if (!btn.toggleGroup) {
45815                 return;
45816             }
45817             var group = groups[btn.toggleGroup];
45818             if (!group) {
45819                 group = groups[btn.toggleGroup] = [];
45820             }
45821             group.push(btn);
45822             btn.on('toggle', toggleGroup);
45823         },
45824
45825         unregister: function(btn) {
45826             if (!btn.toggleGroup) {
45827                 return;
45828             }
45829             var group = groups[btn.toggleGroup];
45830             if (group) {
45831                 Ext.Array.remove(group, btn);
45832                 btn.un('toggle', toggleGroup);
45833             }
45834         },
45835
45836         /**
45837          * Gets the pressed button in the passed group or null
45838          * @param {String} group
45839          * @return {Ext.button.Button}
45840          */
45841         getPressed: function(group) {
45842             var g = groups[group],
45843                 i = 0,
45844                 len;
45845             if (g) {
45846                 for (len = g.length; i < len; i++) {
45847                     if (g[i].pressed === true) {
45848                         return g[i];
45849                     }
45850                 }
45851             }
45852             return null;
45853         }
45854     };
45855 });
45856
45857 /**
45858  * @class Ext.layout.container.boxOverflow.Menu
45859  * @extends Ext.layout.container.boxOverflow.None
45860  * @private
45861  */
45862 Ext.define('Ext.layout.container.boxOverflow.Menu', {
45863
45864     /* Begin Definitions */
45865
45866     extend: 'Ext.layout.container.boxOverflow.None',
45867     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
45868     alternateClassName: 'Ext.layout.boxOverflow.Menu',
45869     
45870     /* End Definitions */
45871
45872     /**
45873      * @cfg {String} afterCtCls
45874      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
45875      * which must always be present at the rightmost edge of the Container
45876      */
45877
45878     /**
45879      * @property noItemsMenuText
45880      * @type String
45881      * HTML fragment to render into the toolbar overflow menu if there are no items to display
45882      */
45883     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
45884
45885     constructor: function(layout) {
45886         var me = this;
45887
45888         me.callParent(arguments);
45889
45890         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
45891         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
45892
45893         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
45894         /**
45895          * @property menuItems
45896          * @type Array
45897          * Array of all items that are currently hidden and should go into the dropdown menu
45898          */
45899         me.menuItems = [];
45900     },
45901     
45902     onRemove: function(comp){
45903         Ext.Array.remove(this.menuItems, comp);
45904     },
45905
45906     handleOverflow: function(calculations, targetSize) {
45907         var me = this,
45908             layout = me.layout,
45909             methodName = 'get' + layout.parallelPrefixCap,
45910             newSize = {},
45911             posArgs = [null, null];
45912
45913         me.callParent(arguments);
45914         this.createMenu(calculations, targetSize);
45915         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
45916         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
45917
45918         // Center the menuTrigger button.
45919         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
45920         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
45921         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
45922
45923         return { targetSize: newSize };
45924     },
45925
45926     /**
45927      * @private
45928      * Called by the layout, when it determines that there is no overflow.
45929      * Also called as an interceptor to the layout's onLayout method to reshow
45930      * previously hidden overflowing items.
45931      */
45932     clearOverflow: function(calculations, targetSize) {
45933         var me = this,
45934             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
45935             items = me.menuItems,
45936             i = 0,
45937             length = items.length,
45938             item;
45939
45940         me.hideTrigger();
45941         for (; i < length; i++) {
45942             items[i].show();
45943         }
45944         items.length = 0;
45945
45946         return targetSize ? {
45947             targetSize: {
45948                 height: targetSize.height,
45949                 width : newWidth
45950             }
45951         } : null;
45952     },
45953
45954     /**
45955      * @private
45956      */
45957     showTrigger: function() {
45958         this.menuTrigger.show();
45959     },
45960
45961     /**
45962      * @private
45963      */
45964     hideTrigger: function() {
45965         if (this.menuTrigger !== undefined) {
45966             this.menuTrigger.hide();
45967         }
45968     },
45969
45970     /**
45971      * @private
45972      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
45973      */
45974     beforeMenuShow: function(menu) {
45975         var me = this,
45976             items = me.menuItems,
45977             i = 0,
45978             len   = items.length,
45979             item,
45980             prev;
45981
45982         var needsSep = function(group, prev){
45983             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
45984         };
45985
45986         me.clearMenu();
45987         menu.removeAll();
45988
45989         for (; i < len; i++) {
45990             item = items[i];
45991
45992             // Do not show a separator as a first item
45993             if (!i && (item instanceof Ext.toolbar.Separator)) {
45994                 continue;
45995             }
45996             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
45997                 menu.add('-');
45998             }
45999
46000             me.addComponentToMenu(menu, item);
46001             prev = item;
46002         }
46003
46004         // put something so the menu isn't empty if no compatible items found
46005         if (menu.items.length < 1) {
46006             menu.add(me.noItemsMenuText);
46007         }
46008     },
46009     
46010     /**
46011      * @private
46012      * Returns a menu config for a given component. This config is used to create a menu item
46013      * to be added to the expander menu
46014      * @param {Ext.Component} component The component to create the config for
46015      * @param {Boolean} hideOnClick Passed through to the menu item
46016      */
46017     createMenuConfig : function(component, hideOnClick) {
46018         var config = Ext.apply({}, component.initialConfig),
46019             group  = component.toggleGroup;
46020
46021         Ext.copyTo(config, component, [
46022             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
46023         ]);
46024
46025         Ext.apply(config, {
46026             text       : component.overflowText || component.text,
46027             hideOnClick: hideOnClick,
46028             destroyMenu: false
46029         });
46030
46031         if (group || component.enableToggle) {
46032             Ext.apply(config, {
46033                 group  : group,
46034                 checked: component.pressed,
46035                 listeners: {
46036                     checkchange: function(item, checked){
46037                         component.toggle(checked);
46038                     }
46039                 }
46040             });
46041         }
46042
46043         delete config.ownerCt;
46044         delete config.xtype;
46045         delete config.id;
46046         return config;
46047     },
46048
46049     /**
46050      * @private
46051      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
46052      * @param {Ext.menu.Menu} menu The menu to add to
46053      * @param {Ext.Component} component The component to add
46054      */
46055     addComponentToMenu : function(menu, component) {
46056         var me = this;
46057         if (component instanceof Ext.toolbar.Separator) {
46058             menu.add('-');
46059         } else if (component.isComponent) {
46060             if (component.isXType('splitbutton')) {
46061                 menu.add(me.createMenuConfig(component, true));
46062
46063             } else if (component.isXType('button')) {
46064                 menu.add(me.createMenuConfig(component, !component.menu));
46065
46066             } else if (component.isXType('buttongroup')) {
46067                 component.items.each(function(item){
46068                      me.addComponentToMenu(menu, item);
46069                 });
46070             } else {
46071                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
46072             }
46073         }
46074     },
46075
46076     /**
46077      * @private
46078      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
46079      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
46080      */
46081     clearMenu : function() {
46082         var menu = this.moreMenu;
46083         if (menu && menu.items) {
46084             menu.items.each(function(item) {
46085                 if (item.menu) {
46086                     delete item.menu;
46087                 }
46088             });
46089         }
46090     },
46091
46092     /**
46093      * @private
46094      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
46095      * in the layout are too wide to fit in the space available
46096      */
46097     createMenu: function(calculations, targetSize) {
46098         var me = this,
46099             layout = me.layout,
46100             startProp = layout.parallelBefore,
46101             sizeProp = layout.parallelPrefix,
46102             available = targetSize[sizeProp],
46103             boxes = calculations.boxes,
46104             i = 0,
46105             len = boxes.length,
46106             box;
46107
46108         if (!me.menuTrigger) {
46109             me.createInnerElements();
46110
46111             /**
46112              * @private
46113              * @property menu
46114              * @type Ext.menu.Menu
46115              * The expand menu - holds items for every item that cannot be shown
46116              * because the container is currently not large enough.
46117              */
46118             me.menu = Ext.create('Ext.menu.Menu', {
46119                 listeners: {
46120                     scope: me,
46121                     beforeshow: me.beforeMenuShow
46122                 }
46123             });
46124
46125             /**
46126              * @private
46127              * @property menuTrigger
46128              * @type Ext.button.Button
46129              * The expand button which triggers the overflow menu to be shown
46130              */
46131             me.menuTrigger = Ext.create('Ext.button.Button', {
46132                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
46133                 iconCls : me.layout.owner.menuTriggerCls,
46134                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
46135                 menu    : me.menu,
46136                 getSplitCls: function() { return '';},
46137                 renderTo: me.afterCt
46138             });
46139         }
46140         me.showTrigger();
46141         available -= me.afterCt.getWidth();
46142
46143         // Hide all items which are off the end, and store them to allow them to be restored
46144         // before each layout operation.
46145         me.menuItems.length = 0;
46146         for (; i < len; i++) {
46147             box = boxes[i];
46148             if (box[startProp] + box[sizeProp] > available) {
46149                 me.menuItems.push(box.component);
46150                 box.component.hide();
46151             }
46152         }
46153     },
46154
46155     /**
46156      * @private
46157      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
46158      * @param {Ext.container.Container} container The Container attached to this Layout instance
46159      * @param {Ext.Element} target The target Element
46160      */
46161     createInnerElements: function() {
46162         var me = this,
46163             target = me.layout.getRenderTarget();
46164
46165         if (!this.afterCt) {
46166             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
46167             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
46168         }
46169     },
46170
46171     /**
46172      * @private
46173      */
46174     destroy: function() {
46175         Ext.destroy(this.menu, this.menuTrigger);
46176     }
46177 });
46178 /**
46179  * This class represents a rectangular region in X,Y space, and performs geometric
46180  * transformations or tests upon the region.
46181  *
46182  * This class may be used to compare the document regions occupied by elements.
46183  */
46184 Ext.define('Ext.util.Region', {
46185
46186     /* Begin Definitions */
46187
46188     requires: ['Ext.util.Offset'],
46189
46190     statics: {
46191         /**
46192          * @static
46193          * Retrieves an Ext.util.Region for a particular element.
46194          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
46195          * @returns {Ext.util.Region} region
46196          */
46197         getRegion: function(el) {
46198             return Ext.fly(el).getPageBox(true);
46199         },
46200
46201         /**
46202          * @static
46203          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
46204          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
46205          * @return {Ext.util.Region} region The Region constructed based on the passed object
46206          */
46207         from: function(o) {
46208             return new this(o.top, o.right, o.bottom, o.left);
46209         }
46210     },
46211
46212     /* End Definitions */
46213
46214     /**
46215      * Creates a region from the bounding sides.
46216      * @param {Number} top Top The topmost pixel of the Region.
46217      * @param {Number} right Right The rightmost pixel of the Region.
46218      * @param {Number} bottom Bottom The bottom pixel of the Region.
46219      * @param {Number} left Left The leftmost pixel of the Region.
46220      */
46221     constructor : function(t, r, b, l) {
46222         var me = this;
46223         me.y = me.top = me[1] = t;
46224         me.right = r;
46225         me.bottom = b;
46226         me.x = me.left = me[0] = l;
46227     },
46228
46229     /**
46230      * Checks if this region completely contains the region that is passed in.
46231      * @param {Ext.util.Region} region
46232      * @return {Boolean}
46233      */
46234     contains : function(region) {
46235         var me = this;
46236         return (region.x >= me.x &&
46237                 region.right <= me.right &&
46238                 region.y >= me.y &&
46239                 region.bottom <= me.bottom);
46240
46241     },
46242
46243     /**
46244      * Checks if this region intersects the region passed in.
46245      * @param {Ext.util.Region} region
46246      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
46247      */
46248     intersect : function(region) {
46249         var me = this,
46250             t = Math.max(me.y, region.y),
46251             r = Math.min(me.right, region.right),
46252             b = Math.min(me.bottom, region.bottom),
46253             l = Math.max(me.x, region.x);
46254
46255         if (b > t && r > l) {
46256             return new this.self(t, r, b, l);
46257         }
46258         else {
46259             return false;
46260         }
46261     },
46262
46263     /**
46264      * Returns the smallest region that contains the current AND targetRegion.
46265      * @param {Ext.util.Region} region
46266      * @return {Ext.util.Region} a new region
46267      */
46268     union : function(region) {
46269         var me = this,
46270             t = Math.min(me.y, region.y),
46271             r = Math.max(me.right, region.right),
46272             b = Math.max(me.bottom, region.bottom),
46273             l = Math.min(me.x, region.x);
46274
46275         return new this.self(t, r, b, l);
46276     },
46277
46278     /**
46279      * Modifies the current region to be constrained to the targetRegion.
46280      * @param {Ext.util.Region} targetRegion
46281      * @return {Ext.util.Region} this
46282      */
46283     constrainTo : function(r) {
46284         var me = this,
46285             constrain = Ext.Number.constrain;
46286         me.top = me.y = constrain(me.top, r.y, r.bottom);
46287         me.bottom = constrain(me.bottom, r.y, r.bottom);
46288         me.left = me.x = constrain(me.left, r.x, r.right);
46289         me.right = constrain(me.right, r.x, r.right);
46290         return me;
46291     },
46292
46293     /**
46294      * Modifies the current region to be adjusted by offsets.
46295      * @param {Number} top top offset
46296      * @param {Number} right right offset
46297      * @param {Number} bottom bottom offset
46298      * @param {Number} left left offset
46299      * @return {Ext.util.Region} this
46300      */
46301     adjust : function(t, r, b, l) {
46302         var me = this;
46303         me.top = me.y += t;
46304         me.left = me.x += l;
46305         me.right += r;
46306         me.bottom += b;
46307         return me;
46308     },
46309
46310     /**
46311      * Get the offset amount of a point outside the region
46312      * @param {String} [axis]
46313      * @param {Ext.util.Point} [p] the point
46314      * @return {Ext.util.Offset}
46315      */
46316     getOutOfBoundOffset: function(axis, p) {
46317         if (!Ext.isObject(axis)) {
46318             if (axis == 'x') {
46319                 return this.getOutOfBoundOffsetX(p);
46320             } else {
46321                 return this.getOutOfBoundOffsetY(p);
46322             }
46323         } else {
46324             p = axis;
46325             var d = Ext.create('Ext.util.Offset');
46326             d.x = this.getOutOfBoundOffsetX(p.x);
46327             d.y = this.getOutOfBoundOffsetY(p.y);
46328             return d;
46329         }
46330
46331     },
46332
46333     /**
46334      * Get the offset amount on the x-axis
46335      * @param {Number} p the offset
46336      * @return {Number}
46337      */
46338     getOutOfBoundOffsetX: function(p) {
46339         if (p <= this.x) {
46340             return this.x - p;
46341         } else if (p >= this.right) {
46342             return this.right - p;
46343         }
46344
46345         return 0;
46346     },
46347
46348     /**
46349      * Get the offset amount on the y-axis
46350      * @param {Number} p the offset
46351      * @return {Number}
46352      */
46353     getOutOfBoundOffsetY: function(p) {
46354         if (p <= this.y) {
46355             return this.y - p;
46356         } else if (p >= this.bottom) {
46357             return this.bottom - p;
46358         }
46359
46360         return 0;
46361     },
46362
46363     /**
46364      * Check whether the point / offset is out of bound
46365      * @param {String} [axis]
46366      * @param {Ext.util.Point/Number} [p] the point / offset
46367      * @return {Boolean}
46368      */
46369     isOutOfBound: function(axis, p) {
46370         if (!Ext.isObject(axis)) {
46371             if (axis == 'x') {
46372                 return this.isOutOfBoundX(p);
46373             } else {
46374                 return this.isOutOfBoundY(p);
46375             }
46376         } else {
46377             p = axis;
46378             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
46379         }
46380     },
46381
46382     /**
46383      * Check whether the offset is out of bound in the x-axis
46384      * @param {Number} p the offset
46385      * @return {Boolean}
46386      */
46387     isOutOfBoundX: function(p) {
46388         return (p < this.x || p > this.right);
46389     },
46390
46391     /**
46392      * Check whether the offset is out of bound in the y-axis
46393      * @param {Number} p the offset
46394      * @return {Boolean}
46395      */
46396     isOutOfBoundY: function(p) {
46397         return (p < this.y || p > this.bottom);
46398     },
46399
46400     /**
46401      * Restrict a point within the region by a certain factor.
46402      * @param {String} [axis]
46403      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
46404      * @param {Number} [factor]
46405      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
46406      * @private
46407      */
46408     restrict: function(axis, p, factor) {
46409         if (Ext.isObject(axis)) {
46410             var newP;
46411
46412             factor = p;
46413             p = axis;
46414
46415             if (p.copy) {
46416                 newP = p.copy();
46417             }
46418             else {
46419                 newP = {
46420                     x: p.x,
46421                     y: p.y
46422                 };
46423             }
46424
46425             newP.x = this.restrictX(p.x, factor);
46426             newP.y = this.restrictY(p.y, factor);
46427             return newP;
46428         } else {
46429             if (axis == 'x') {
46430                 return this.restrictX(p, factor);
46431             } else {
46432                 return this.restrictY(p, factor);
46433             }
46434         }
46435     },
46436
46437     /**
46438      * Restrict an offset within the region by a certain factor, on the x-axis
46439      * @param {Number} p
46440      * @param {Number} [factor=1] The factor.
46441      * @return {Number}
46442      * @private
46443      */
46444     restrictX : function(p, factor) {
46445         if (!factor) {
46446             factor = 1;
46447         }
46448
46449         if (p <= this.x) {
46450             p -= (p - this.x) * factor;
46451         }
46452         else if (p >= this.right) {
46453             p -= (p - this.right) * factor;
46454         }
46455         return p;
46456     },
46457
46458     /**
46459      * Restrict an offset within the region by a certain factor, on the y-axis
46460      * @param {Number} p
46461      * @param {Number} [factor] The factor, defaults to 1
46462      * @return {Number}
46463      * @private
46464      */
46465     restrictY : function(p, factor) {
46466         if (!factor) {
46467             factor = 1;
46468         }
46469
46470         if (p <= this.y) {
46471             p -= (p - this.y) * factor;
46472         }
46473         else if (p >= this.bottom) {
46474             p -= (p - this.bottom) * factor;
46475         }
46476         return p;
46477     },
46478
46479     /**
46480      * Get the width / height of this region
46481      * @return {Object} an object with width and height properties
46482      * @private
46483      */
46484     getSize: function() {
46485         return {
46486             width: this.right - this.x,
46487             height: this.bottom - this.y
46488         };
46489     },
46490
46491     /**
46492      * Create a copy of this Region.
46493      * @return {Ext.util.Region}
46494      */
46495     copy: function() {
46496         return new this.self(this.y, this.right, this.bottom, this.x);
46497     },
46498
46499     /**
46500      * Copy the values of another Region to this Region
46501      * @param {Ext.util.Region} p The region to copy from.
46502      * @return {Ext.util.Region} This Region
46503      */
46504     copyFrom: function(p) {
46505         var me = this;
46506         me.top = me.y = me[1] = p.y;
46507         me.right = p.right;
46508         me.bottom = p.bottom;
46509         me.left = me.x = me[0] = p.x;
46510
46511         return this;
46512     },
46513
46514     /*
46515      * Dump this to an eye-friendly string, great for debugging
46516      * @return {String}
46517      */
46518     toString: function() {
46519         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
46520     },
46521
46522     /**
46523      * Translate this region by the given offset amount
46524      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
46525      * Or the x value is using the two argument form.
46526      * @param {Number} y The y value unless using an Offset object.
46527      * @return {Ext.util.Region} this This Region
46528      */
46529     translateBy: function(x, y) {
46530         if (arguments.length == 1) {
46531             y = x.y;
46532             x = x.x;
46533         }
46534         var me = this;
46535         me.top = me.y += y;
46536         me.right += x;
46537         me.bottom += y;
46538         me.left = me.x += x;
46539
46540         return me;
46541     },
46542
46543     /**
46544      * Round all the properties of this region
46545      * @return {Ext.util.Region} this This Region
46546      */
46547     round: function() {
46548         var me = this;
46549         me.top = me.y = Math.round(me.y);
46550         me.right = Math.round(me.right);
46551         me.bottom = Math.round(me.bottom);
46552         me.left = me.x = Math.round(me.x);
46553
46554         return me;
46555     },
46556
46557     /**
46558      * Check whether this region is equivalent to the given region
46559      * @param {Ext.util.Region} region The region to compare with
46560      * @return {Boolean}
46561      */
46562     equals: function(region) {
46563         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
46564     }
46565 });
46566
46567 /*
46568  * This is a derivative of the similarly named class in the YUI Library.
46569  * The original license:
46570  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
46571  * Code licensed under the BSD License:
46572  * http://developer.yahoo.net/yui/license.txt
46573  */
46574
46575
46576 /**
46577  * @class Ext.dd.DragDropManager
46578  * DragDropManager is a singleton that tracks the element interaction for
46579  * all DragDrop items in the window.  Generally, you will not call
46580  * this class directly, but it does have helper methods that could
46581  * be useful in your DragDrop implementations.
46582  * @singleton
46583  */
46584 Ext.define('Ext.dd.DragDropManager', {
46585     singleton: true,
46586
46587     requires: ['Ext.util.Region'],
46588
46589     uses: ['Ext.tip.QuickTipManager'],
46590
46591     // shorter ClassName, to save bytes and use internally
46592     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
46593
46594     /**
46595      * Two dimensional Array of registered DragDrop objects.  The first
46596      * dimension is the DragDrop item group, the second the DragDrop
46597      * object.
46598      * @property ids
46599      * @type String[]
46600      * @private
46601      */
46602     ids: {},
46603
46604     /**
46605      * Array of element ids defined as drag handles.  Used to determine
46606      * if the element that generated the mousedown event is actually the
46607      * handle and not the html element itself.
46608      * @property handleIds
46609      * @type String[]
46610      * @private
46611      */
46612     handleIds: {},
46613
46614     /**
46615      * the DragDrop object that is currently being dragged
46616      * @property {Ext.dd.DragDrop} dragCurrent
46617      * @private
46618      **/
46619     dragCurrent: null,
46620
46621     /**
46622      * the DragDrop object(s) that are being hovered over
46623      * @property {Ext.dd.DragDrop[]} dragOvers
46624      * @private
46625      */
46626     dragOvers: {},
46627
46628     /**
46629      * the X distance between the cursor and the object being dragged
46630      * @property deltaX
46631      * @type Number
46632      * @private
46633      */
46634     deltaX: 0,
46635
46636     /**
46637      * the Y distance between the cursor and the object being dragged
46638      * @property deltaY
46639      * @type Number
46640      * @private
46641      */
46642     deltaY: 0,
46643
46644     /**
46645      * Flag to determine if we should prevent the default behavior of the
46646      * events we define. By default this is true, but this can be set to
46647      * false if you need the default behavior (not recommended)
46648      * @property preventDefault
46649      * @type Boolean
46650      */
46651     preventDefault: true,
46652
46653     /**
46654      * Flag to determine if we should stop the propagation of the events
46655      * we generate. This is true by default but you may want to set it to
46656      * false if the html element contains other features that require the
46657      * mouse click.
46658      * @property stopPropagation
46659      * @type Boolean
46660      */
46661     stopPropagation: true,
46662
46663     /**
46664      * Internal flag that is set to true when drag and drop has been
46665      * intialized
46666      * @property initialized
46667      * @private
46668      */
46669     initialized: false,
46670
46671     /**
46672      * All drag and drop can be disabled.
46673      * @property locked
46674      * @private
46675      */
46676     locked: false,
46677
46678     /**
46679      * Called the first time an element is registered.
46680      * @method init
46681      * @private
46682      */
46683     init: function() {
46684         this.initialized = true;
46685     },
46686
46687     /**
46688      * In point mode, drag and drop interaction is defined by the
46689      * location of the cursor during the drag/drop
46690      * @property POINT
46691      * @type Number
46692      */
46693     POINT: 0,
46694
46695     /**
46696      * In intersect mode, drag and drop interaction is defined by the
46697      * overlap of two or more drag and drop objects.
46698      * @property INTERSECT
46699      * @type Number
46700      */
46701     INTERSECT: 1,
46702
46703     /**
46704      * The current drag and drop mode.  Default: POINT
46705      * @property mode
46706      * @type Number
46707      */
46708     mode: 0,
46709
46710     /**
46711      * Runs method on all drag and drop objects
46712      * @method _execOnAll
46713      * @private
46714      */
46715     _execOnAll: function(sMethod, args) {
46716         for (var i in this.ids) {
46717             for (var j in this.ids[i]) {
46718                 var oDD = this.ids[i][j];
46719                 if (! this.isTypeOfDD(oDD)) {
46720                     continue;
46721                 }
46722                 oDD[sMethod].apply(oDD, args);
46723             }
46724         }
46725     },
46726
46727     /**
46728      * Drag and drop initialization.  Sets up the global event handlers
46729      * @method _onLoad
46730      * @private
46731      */
46732     _onLoad: function() {
46733
46734         this.init();
46735
46736         var Event = Ext.EventManager;
46737         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
46738         Event.on(document, "mousemove", this.handleMouseMove, this, true);
46739         Event.on(window,   "unload",    this._onUnload, this, true);
46740         Event.on(window,   "resize",    this._onResize, this, true);
46741         // Event.on(window,   "mouseout",    this._test);
46742
46743     },
46744
46745     /**
46746      * Reset constraints on all drag and drop objs
46747      * @method _onResize
46748      * @private
46749      */
46750     _onResize: function(e) {
46751         this._execOnAll("resetConstraints", []);
46752     },
46753
46754     /**
46755      * Lock all drag and drop functionality
46756      * @method lock
46757      */
46758     lock: function() { this.locked = true; },
46759
46760     /**
46761      * Unlock all drag and drop functionality
46762      * @method unlock
46763      */
46764     unlock: function() { this.locked = false; },
46765
46766     /**
46767      * Is drag and drop locked?
46768      * @method isLocked
46769      * @return {Boolean} True if drag and drop is locked, false otherwise.
46770      */
46771     isLocked: function() { return this.locked; },
46772
46773     /**
46774      * Location cache that is set for all drag drop objects when a drag is
46775      * initiated, cleared when the drag is finished.
46776      * @property locationCache
46777      * @private
46778      */
46779     locationCache: {},
46780
46781     /**
46782      * Set useCache to false if you want to force object the lookup of each
46783      * drag and drop linked element constantly during a drag.
46784      * @property useCache
46785      * @type Boolean
46786      */
46787     useCache: true,
46788
46789     /**
46790      * The number of pixels that the mouse needs to move after the
46791      * mousedown before the drag is initiated.  Default=3;
46792      * @property clickPixelThresh
46793      * @type Number
46794      */
46795     clickPixelThresh: 3,
46796
46797     /**
46798      * The number of milliseconds after the mousedown event to initiate the
46799      * drag if we don't get a mouseup event. Default=350
46800      * @property clickTimeThresh
46801      * @type Number
46802      */
46803     clickTimeThresh: 350,
46804
46805     /**
46806      * Flag that indicates that either the drag pixel threshold or the
46807      * mousdown time threshold has been met
46808      * @property dragThreshMet
46809      * @type Boolean
46810      * @private
46811      */
46812     dragThreshMet: false,
46813
46814     /**
46815      * Timeout used for the click time threshold
46816      * @property clickTimeout
46817      * @type Object
46818      * @private
46819      */
46820     clickTimeout: null,
46821
46822     /**
46823      * The X position of the mousedown event stored for later use when a
46824      * drag threshold is met.
46825      * @property startX
46826      * @type Number
46827      * @private
46828      */
46829     startX: 0,
46830
46831     /**
46832      * The Y position of the mousedown event stored for later use when a
46833      * drag threshold is met.
46834      * @property startY
46835      * @type Number
46836      * @private
46837      */
46838     startY: 0,
46839
46840     /**
46841      * Each DragDrop instance must be registered with the DragDropManager.
46842      * This is executed in DragDrop.init()
46843      * @method regDragDrop
46844      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
46845      * @param {String} sGroup the name of the group this element belongs to
46846      */
46847     regDragDrop: function(oDD, sGroup) {
46848         if (!this.initialized) { this.init(); }
46849
46850         if (!this.ids[sGroup]) {
46851             this.ids[sGroup] = {};
46852         }
46853         this.ids[sGroup][oDD.id] = oDD;
46854     },
46855
46856     /**
46857      * Removes the supplied dd instance from the supplied group. Executed
46858      * by DragDrop.removeFromGroup, so don't call this function directly.
46859      * @method removeDDFromGroup
46860      * @private
46861      */
46862     removeDDFromGroup: function(oDD, sGroup) {
46863         if (!this.ids[sGroup]) {
46864             this.ids[sGroup] = {};
46865         }
46866
46867         var obj = this.ids[sGroup];
46868         if (obj && obj[oDD.id]) {
46869             delete obj[oDD.id];
46870         }
46871     },
46872
46873     /**
46874      * Unregisters a drag and drop item.  This is executed in
46875      * DragDrop.unreg, use that method instead of calling this directly.
46876      * @method _remove
46877      * @private
46878      */
46879     _remove: function(oDD) {
46880         for (var g in oDD.groups) {
46881             if (g && this.ids[g] && this.ids[g][oDD.id]) {
46882                 delete this.ids[g][oDD.id];
46883             }
46884         }
46885         delete this.handleIds[oDD.id];
46886     },
46887
46888     /**
46889      * Each DragDrop handle element must be registered.  This is done
46890      * automatically when executing DragDrop.setHandleElId()
46891      * @method regHandle
46892      * @param {String} sDDId the DragDrop id this element is a handle for
46893      * @param {String} sHandleId the id of the element that is the drag
46894      * handle
46895      */
46896     regHandle: function(sDDId, sHandleId) {
46897         if (!this.handleIds[sDDId]) {
46898             this.handleIds[sDDId] = {};
46899         }
46900         this.handleIds[sDDId][sHandleId] = sHandleId;
46901     },
46902
46903     /**
46904      * Utility function to determine if a given element has been
46905      * registered as a drag drop item.
46906      * @method isDragDrop
46907      * @param {String} id the element id to check
46908      * @return {Boolean} true if this element is a DragDrop item,
46909      * false otherwise
46910      */
46911     isDragDrop: function(id) {
46912         return ( this.getDDById(id) ) ? true : false;
46913     },
46914
46915     /**
46916      * Returns the drag and drop instances that are in all groups the
46917      * passed in instance belongs to.
46918      * @method getRelated
46919      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
46920      * @param {Boolean} bTargetsOnly if true, only return targetable objs
46921      * @return {Ext.dd.DragDrop[]} the related instances
46922      */
46923     getRelated: function(p_oDD, bTargetsOnly) {
46924         var oDDs = [];
46925         for (var i in p_oDD.groups) {
46926             for (var j in this.ids[i]) {
46927                 var dd = this.ids[i][j];
46928                 if (! this.isTypeOfDD(dd)) {
46929                     continue;
46930                 }
46931                 if (!bTargetsOnly || dd.isTarget) {
46932                     oDDs[oDDs.length] = dd;
46933                 }
46934             }
46935         }
46936
46937         return oDDs;
46938     },
46939
46940     /**
46941      * Returns true if the specified dd target is a legal target for
46942      * the specifice drag obj
46943      * @method isLegalTarget
46944      * @param {Ext.dd.DragDrop} oDD the drag obj
46945      * @param {Ext.dd.DragDrop} oTargetDD the target
46946      * @return {Boolean} true if the target is a legal target for the
46947      * dd obj
46948      */
46949     isLegalTarget: function (oDD, oTargetDD) {
46950         var targets = this.getRelated(oDD, true);
46951         for (var i=0, len=targets.length;i<len;++i) {
46952             if (targets[i].id == oTargetDD.id) {
46953                 return true;
46954             }
46955         }
46956
46957         return false;
46958     },
46959
46960     /**
46961      * My goal is to be able to transparently determine if an object is
46962      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
46963      * returns "object", oDD.constructor.toString() always returns
46964      * "DragDrop" and not the name of the subclass.  So for now it just
46965      * evaluates a well-known variable in DragDrop.
46966      * @method isTypeOfDD
46967      * @param {Object} the object to evaluate
46968      * @return {Boolean} true if typeof oDD = DragDrop
46969      */
46970     isTypeOfDD: function (oDD) {
46971         return (oDD && oDD.__ygDragDrop);
46972     },
46973
46974     /**
46975      * Utility function to determine if a given element has been
46976      * registered as a drag drop handle for the given Drag Drop object.
46977      * @method isHandle
46978      * @param {String} id the element id to check
46979      * @return {Boolean} true if this element is a DragDrop handle, false
46980      * otherwise
46981      */
46982     isHandle: function(sDDId, sHandleId) {
46983         return ( this.handleIds[sDDId] &&
46984                         this.handleIds[sDDId][sHandleId] );
46985     },
46986
46987     /**
46988      * Returns the DragDrop instance for a given id
46989      * @method getDDById
46990      * @param {String} id the id of the DragDrop object
46991      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
46992      */
46993     getDDById: function(id) {
46994         for (var i in this.ids) {
46995             if (this.ids[i][id]) {
46996                 return this.ids[i][id];
46997             }
46998         }
46999         return null;
47000     },
47001
47002     /**
47003      * Fired after a registered DragDrop object gets the mousedown event.
47004      * Sets up the events required to track the object being dragged
47005      * @method handleMouseDown
47006      * @param {Event} e the event
47007      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
47008      * @private
47009      */
47010     handleMouseDown: function(e, oDD) {
47011         if(Ext.tip.QuickTipManager){
47012             Ext.tip.QuickTipManager.ddDisable();
47013         }
47014         if(this.dragCurrent){
47015             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
47016             // so clean up first to avoid breaking the next drag
47017             this.handleMouseUp(e);
47018         }
47019
47020         this.currentTarget = e.getTarget();
47021         this.dragCurrent = oDD;
47022
47023         var el = oDD.getEl();
47024
47025         // track start position
47026         this.startX = e.getPageX();
47027         this.startY = e.getPageY();
47028
47029         this.deltaX = this.startX - el.offsetLeft;
47030         this.deltaY = this.startY - el.offsetTop;
47031
47032         this.dragThreshMet = false;
47033
47034         this.clickTimeout = setTimeout(
47035                 function() {
47036                     var DDM = Ext.dd.DragDropManager;
47037                     DDM.startDrag(DDM.startX, DDM.startY);
47038                 },
47039                 this.clickTimeThresh );
47040     },
47041
47042     /**
47043      * Fired when either the drag pixel threshol or the mousedown hold
47044      * time threshold has been met.
47045      * @method startDrag
47046      * @param {Number} x the X position of the original mousedown
47047      * @param {Number} y the Y position of the original mousedown
47048      */
47049     startDrag: function(x, y) {
47050         clearTimeout(this.clickTimeout);
47051         if (this.dragCurrent) {
47052             this.dragCurrent.b4StartDrag(x, y);
47053             this.dragCurrent.startDrag(x, y);
47054         }
47055         this.dragThreshMet = true;
47056     },
47057
47058     /**
47059      * Internal function to handle the mouseup event.  Will be invoked
47060      * from the context of the document.
47061      * @method handleMouseUp
47062      * @param {Event} e the event
47063      * @private
47064      */
47065     handleMouseUp: function(e) {
47066
47067         if(Ext.tip && Ext.tip.QuickTipManager){
47068             Ext.tip.QuickTipManager.ddEnable();
47069         }
47070         if (! this.dragCurrent) {
47071             return;
47072         }
47073
47074         clearTimeout(this.clickTimeout);
47075
47076         if (this.dragThreshMet) {
47077             this.fireEvents(e, true);
47078         } else {
47079         }
47080
47081         this.stopDrag(e);
47082
47083         this.stopEvent(e);
47084     },
47085
47086     /**
47087      * Utility to stop event propagation and event default, if these
47088      * features are turned on.
47089      * @method stopEvent
47090      * @param {Event} e the event as returned by this.getEvent()
47091      */
47092     stopEvent: function(e){
47093         if(this.stopPropagation) {
47094             e.stopPropagation();
47095         }
47096
47097         if (this.preventDefault) {
47098             e.preventDefault();
47099         }
47100     },
47101
47102     /**
47103      * Internal function to clean up event handlers after the drag
47104      * operation is complete
47105      * @method stopDrag
47106      * @param {Event} e the event
47107      * @private
47108      */
47109     stopDrag: function(e) {
47110         // Fire the drag end event for the item that was dragged
47111         if (this.dragCurrent) {
47112             if (this.dragThreshMet) {
47113                 this.dragCurrent.b4EndDrag(e);
47114                 this.dragCurrent.endDrag(e);
47115             }
47116
47117             this.dragCurrent.onMouseUp(e);
47118         }
47119
47120         this.dragCurrent = null;
47121         this.dragOvers = {};
47122     },
47123
47124     /**
47125      * Internal function to handle the mousemove event.  Will be invoked
47126      * from the context of the html element.
47127      *
47128      * @TODO figure out what we can do about mouse events lost when the
47129      * user drags objects beyond the window boundary.  Currently we can
47130      * detect this in internet explorer by verifying that the mouse is
47131      * down during the mousemove event.  Firefox doesn't give us the
47132      * button state on the mousemove event.
47133      * @method handleMouseMove
47134      * @param {Event} e the event
47135      * @private
47136      */
47137     handleMouseMove: function(e) {
47138         if (! this.dragCurrent) {
47139             return true;
47140         }
47141         // var button = e.which || e.button;
47142
47143         // check for IE mouseup outside of page boundary
47144         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
47145             this.stopEvent(e);
47146             return this.handleMouseUp(e);
47147         }
47148
47149         if (!this.dragThreshMet) {
47150             var diffX = Math.abs(this.startX - e.getPageX());
47151             var diffY = Math.abs(this.startY - e.getPageY());
47152             if (diffX > this.clickPixelThresh ||
47153                         diffY > this.clickPixelThresh) {
47154                 this.startDrag(this.startX, this.startY);
47155             }
47156         }
47157
47158         if (this.dragThreshMet) {
47159             this.dragCurrent.b4Drag(e);
47160             this.dragCurrent.onDrag(e);
47161             if(!this.dragCurrent.moveOnly){
47162                 this.fireEvents(e, false);
47163             }
47164         }
47165
47166         this.stopEvent(e);
47167
47168         return true;
47169     },
47170
47171     /**
47172      * Iterates over all of the DragDrop elements to find ones we are
47173      * hovering over or dropping on
47174      * @method fireEvents
47175      * @param {Event} e the event
47176      * @param {Boolean} isDrop is this a drop op or a mouseover op?
47177      * @private
47178      */
47179     fireEvents: function(e, isDrop) {
47180         var dc = this.dragCurrent;
47181
47182         // If the user did the mouse up outside of the window, we could
47183         // get here even though we have ended the drag.
47184         if (!dc || dc.isLocked()) {
47185             return;
47186         }
47187
47188         var pt = e.getPoint();
47189
47190         // cache the previous dragOver array
47191         var oldOvers = [];
47192
47193         var outEvts   = [];
47194         var overEvts  = [];
47195         var dropEvts  = [];
47196         var enterEvts = [];
47197
47198         // Check to see if the object(s) we were hovering over is no longer
47199         // being hovered over so we can fire the onDragOut event
47200         for (var i in this.dragOvers) {
47201
47202             var ddo = this.dragOvers[i];
47203
47204             if (! this.isTypeOfDD(ddo)) {
47205                 continue;
47206             }
47207
47208             if (! this.isOverTarget(pt, ddo, this.mode)) {
47209                 outEvts.push( ddo );
47210             }
47211
47212             oldOvers[i] = true;
47213             delete this.dragOvers[i];
47214         }
47215
47216         for (var sGroup in dc.groups) {
47217
47218             if ("string" != typeof sGroup) {
47219                 continue;
47220             }
47221
47222             for (i in this.ids[sGroup]) {
47223                 var oDD = this.ids[sGroup][i];
47224                 if (! this.isTypeOfDD(oDD)) {
47225                     continue;
47226                 }
47227
47228                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
47229                     if (this.isOverTarget(pt, oDD, this.mode)) {
47230                         // look for drop interactions
47231                         if (isDrop) {
47232                             dropEvts.push( oDD );
47233                         // look for drag enter and drag over interactions
47234                         } else {
47235
47236                             // initial drag over: dragEnter fires
47237                             if (!oldOvers[oDD.id]) {
47238                                 enterEvts.push( oDD );
47239                             // subsequent drag overs: dragOver fires
47240                             } else {
47241                                 overEvts.push( oDD );
47242                             }
47243
47244                             this.dragOvers[oDD.id] = oDD;
47245                         }
47246                     }
47247                 }
47248             }
47249         }
47250
47251         if (this.mode) {
47252             if (outEvts.length) {
47253                 dc.b4DragOut(e, outEvts);
47254                 dc.onDragOut(e, outEvts);
47255             }
47256
47257             if (enterEvts.length) {
47258                 dc.onDragEnter(e, enterEvts);
47259             }
47260
47261             if (overEvts.length) {
47262                 dc.b4DragOver(e, overEvts);
47263                 dc.onDragOver(e, overEvts);
47264             }
47265
47266             if (dropEvts.length) {
47267                 dc.b4DragDrop(e, dropEvts);
47268                 dc.onDragDrop(e, dropEvts);
47269             }
47270
47271         } else {
47272             // fire dragout events
47273             var len = 0;
47274             for (i=0, len=outEvts.length; i<len; ++i) {
47275                 dc.b4DragOut(e, outEvts[i].id);
47276                 dc.onDragOut(e, outEvts[i].id);
47277             }
47278
47279             // fire enter events
47280             for (i=0,len=enterEvts.length; i<len; ++i) {
47281                 // dc.b4DragEnter(e, oDD.id);
47282                 dc.onDragEnter(e, enterEvts[i].id);
47283             }
47284
47285             // fire over events
47286             for (i=0,len=overEvts.length; i<len; ++i) {
47287                 dc.b4DragOver(e, overEvts[i].id);
47288                 dc.onDragOver(e, overEvts[i].id);
47289             }
47290
47291             // fire drop events
47292             for (i=0, len=dropEvts.length; i<len; ++i) {
47293                 dc.b4DragDrop(e, dropEvts[i].id);
47294                 dc.onDragDrop(e, dropEvts[i].id);
47295             }
47296
47297         }
47298
47299         // notify about a drop that did not find a target
47300         if (isDrop && !dropEvts.length) {
47301             dc.onInvalidDrop(e);
47302         }
47303
47304     },
47305
47306     /**
47307      * Helper function for getting the best match from the list of drag
47308      * and drop objects returned by the drag and drop events when we are
47309      * in INTERSECT mode.  It returns either the first object that the
47310      * cursor is over, or the object that has the greatest overlap with
47311      * the dragged element.
47312      * @method getBestMatch
47313      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
47314      * targeted
47315      * @return {Ext.dd.DragDrop}       The best single match
47316      */
47317     getBestMatch: function(dds) {
47318         var winner = null;
47319         // Return null if the input is not what we expect
47320         //if (!dds || !dds.length || dds.length == 0) {
47321            // winner = null;
47322         // If there is only one item, it wins
47323         //} else if (dds.length == 1) {
47324
47325         var len = dds.length;
47326
47327         if (len == 1) {
47328             winner = dds[0];
47329         } else {
47330             // Loop through the targeted items
47331             for (var i=0; i<len; ++i) {
47332                 var dd = dds[i];
47333                 // If the cursor is over the object, it wins.  If the
47334                 // cursor is over multiple matches, the first one we come
47335                 // to wins.
47336                 if (dd.cursorIsOver) {
47337                     winner = dd;
47338                     break;
47339                 // Otherwise the object with the most overlap wins
47340                 } else {
47341                     if (!winner ||
47342                         winner.overlap.getArea() < dd.overlap.getArea()) {
47343                         winner = dd;
47344                     }
47345                 }
47346             }
47347         }
47348
47349         return winner;
47350     },
47351
47352     /**
47353      * Refreshes the cache of the top-left and bottom-right points of the
47354      * drag and drop objects in the specified group(s).  This is in the
47355      * format that is stored in the drag and drop instance, so typical
47356      * usage is:
47357      * <code>
47358      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
47359      * </code>
47360      * Alternatively:
47361      * <code>
47362      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
47363      * </code>
47364      * @TODO this really should be an indexed array.  Alternatively this
47365      * method could accept both.
47366      * @method refreshCache
47367      * @param {Object} groups an associative array of groups to refresh
47368      */
47369     refreshCache: function(groups) {
47370         for (var sGroup in groups) {
47371             if ("string" != typeof sGroup) {
47372                 continue;
47373             }
47374             for (var i in this.ids[sGroup]) {
47375                 var oDD = this.ids[sGroup][i];
47376
47377                 if (this.isTypeOfDD(oDD)) {
47378                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
47379                     var loc = this.getLocation(oDD);
47380                     if (loc) {
47381                         this.locationCache[oDD.id] = loc;
47382                     } else {
47383                         delete this.locationCache[oDD.id];
47384                         // this will unregister the drag and drop object if
47385                         // the element is not in a usable state
47386                         // oDD.unreg();
47387                     }
47388                 }
47389             }
47390         }
47391     },
47392
47393     /**
47394      * This checks to make sure an element exists and is in the DOM.  The
47395      * main purpose is to handle cases where innerHTML is used to remove
47396      * drag and drop objects from the DOM.  IE provides an 'unspecified
47397      * error' when trying to access the offsetParent of such an element
47398      * @method verifyEl
47399      * @param {HTMLElement} el the element to check
47400      * @return {Boolean} true if the element looks usable
47401      */
47402     verifyEl: function(el) {
47403         if (el) {
47404             var parent;
47405             if(Ext.isIE){
47406                 try{
47407                     parent = el.offsetParent;
47408                 }catch(e){}
47409             }else{
47410                 parent = el.offsetParent;
47411             }
47412             if (parent) {
47413                 return true;
47414             }
47415         }
47416
47417         return false;
47418     },
47419
47420     /**
47421      * Returns a Region object containing the drag and drop element's position
47422      * and size, including the padding configured for it
47423      * @method getLocation
47424      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
47425      * @return {Ext.util.Region} a Region object representing the total area
47426      * the element occupies, including any padding
47427      * the instance is configured for.
47428      */
47429     getLocation: function(oDD) {
47430         if (! this.isTypeOfDD(oDD)) {
47431             return null;
47432         }
47433
47434         //delegate getLocation method to the
47435         //drag and drop target.
47436         if (oDD.getRegion) {
47437             return oDD.getRegion();
47438         }
47439
47440         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
47441
47442         try {
47443             pos= Ext.Element.getXY(el);
47444         } catch (e) { }
47445
47446         if (!pos) {
47447             return null;
47448         }
47449
47450         x1 = pos[0];
47451         x2 = x1 + el.offsetWidth;
47452         y1 = pos[1];
47453         y2 = y1 + el.offsetHeight;
47454
47455         t = y1 - oDD.padding[0];
47456         r = x2 + oDD.padding[1];
47457         b = y2 + oDD.padding[2];
47458         l = x1 - oDD.padding[3];
47459
47460         return Ext.create('Ext.util.Region', t, r, b, l);
47461     },
47462
47463     /**
47464      * Checks the cursor location to see if it over the target
47465      * @method isOverTarget
47466      * @param {Ext.util.Point} pt The point to evaluate
47467      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
47468      * @return {Boolean} true if the mouse is over the target
47469      * @private
47470      */
47471     isOverTarget: function(pt, oTarget, intersect) {
47472         // use cache if available
47473         var loc = this.locationCache[oTarget.id];
47474         if (!loc || !this.useCache) {
47475             loc = this.getLocation(oTarget);
47476             this.locationCache[oTarget.id] = loc;
47477
47478         }
47479
47480         if (!loc) {
47481             return false;
47482         }
47483
47484         oTarget.cursorIsOver = loc.contains( pt );
47485
47486         // DragDrop is using this as a sanity check for the initial mousedown
47487         // in this case we are done.  In POINT mode, if the drag obj has no
47488         // contraints, we are also done. Otherwise we need to evaluate the
47489         // location of the target as related to the actual location of the
47490         // dragged element.
47491         var dc = this.dragCurrent;
47492         if (!dc || !dc.getTargetCoord ||
47493                 (!intersect && !dc.constrainX && !dc.constrainY)) {
47494             return oTarget.cursorIsOver;
47495         }
47496
47497         oTarget.overlap = null;
47498
47499         // Get the current location of the drag element, this is the
47500         // location of the mouse event less the delta that represents
47501         // where the original mousedown happened on the element.  We
47502         // need to consider constraints and ticks as well.
47503         var pos = dc.getTargetCoord(pt.x, pt.y);
47504
47505         var el = dc.getDragEl();
47506         var curRegion = Ext.create('Ext.util.Region', pos.y,
47507                                                pos.x + el.offsetWidth,
47508                                                pos.y + el.offsetHeight,
47509                                                pos.x );
47510
47511         var overlap = curRegion.intersect(loc);
47512
47513         if (overlap) {
47514             oTarget.overlap = overlap;
47515             return (intersect) ? true : oTarget.cursorIsOver;
47516         } else {
47517             return false;
47518         }
47519     },
47520
47521     /**
47522      * unload event handler
47523      * @method _onUnload
47524      * @private
47525      */
47526     _onUnload: function(e, me) {
47527         Ext.dd.DragDropManager.unregAll();
47528     },
47529
47530     /**
47531      * Cleans up the drag and drop events and objects.
47532      * @method unregAll
47533      * @private
47534      */
47535     unregAll: function() {
47536
47537         if (this.dragCurrent) {
47538             this.stopDrag();
47539             this.dragCurrent = null;
47540         }
47541
47542         this._execOnAll("unreg", []);
47543
47544         for (var i in this.elementCache) {
47545             delete this.elementCache[i];
47546         }
47547
47548         this.elementCache = {};
47549         this.ids = {};
47550     },
47551
47552     /**
47553      * A cache of DOM elements
47554      * @property elementCache
47555      * @private
47556      */
47557     elementCache: {},
47558
47559     /**
47560      * Get the wrapper for the DOM element specified
47561      * @method getElWrapper
47562      * @param {String} id the id of the element to get
47563      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
47564      * @private
47565      * @deprecated This wrapper isn't that useful
47566      */
47567     getElWrapper: function(id) {
47568         var oWrapper = this.elementCache[id];
47569         if (!oWrapper || !oWrapper.el) {
47570             oWrapper = this.elementCache[id] =
47571                 new this.ElementWrapper(Ext.getDom(id));
47572         }
47573         return oWrapper;
47574     },
47575
47576     /**
47577      * Returns the actual DOM element
47578      * @method getElement
47579      * @param {String} id the id of the elment to get
47580      * @return {Object} The element
47581      * @deprecated use Ext.lib.Ext.getDom instead
47582      */
47583     getElement: function(id) {
47584         return Ext.getDom(id);
47585     },
47586
47587     /**
47588      * Returns the style property for the DOM element (i.e.,
47589      * document.getElById(id).style)
47590      * @method getCss
47591      * @param {String} id the id of the elment to get
47592      * @return {Object} The style property of the element
47593      */
47594     getCss: function(id) {
47595         var el = Ext.getDom(id);
47596         return (el) ? el.style : null;
47597     },
47598
47599     /**
47600      * @class Ext.dd.DragDropManager.ElementWrapper
47601      * Inner class for cached elements
47602      * @private
47603      * @deprecated
47604      */
47605     ElementWrapper: function(el) {
47606         /**
47607          * The element
47608          * @property el
47609          */
47610         this.el = el || null;
47611         /**
47612          * The element id
47613          * @property id
47614          */
47615         this.id = this.el && el.id;
47616         /**
47617          * A reference to the style property
47618          * @property css
47619          */
47620         this.css = this.el && el.style;
47621     },
47622
47623     // The DragDropManager class continues
47624     /** @class Ext.dd.DragDropManager */
47625
47626     /**
47627      * Returns the X position of an html element
47628      * @param {HTMLElement} el the element for which to get the position
47629      * @return {Number} the X coordinate
47630      */
47631     getPosX: function(el) {
47632         return Ext.Element.getX(el);
47633     },
47634
47635     /**
47636      * Returns the Y position of an html element
47637      * @param {HTMLElement} el the element for which to get the position
47638      * @return {Number} the Y coordinate
47639      */
47640     getPosY: function(el) {
47641         return Ext.Element.getY(el);
47642     },
47643
47644     /**
47645      * Swap two nodes.  In IE, we use the native method, for others we
47646      * emulate the IE behavior
47647      * @param {HTMLElement} n1 the first node to swap
47648      * @param {HTMLElement} n2 the other node to swap
47649      */
47650     swapNode: function(n1, n2) {
47651         if (n1.swapNode) {
47652             n1.swapNode(n2);
47653         } else {
47654             var p = n2.parentNode;
47655             var s = n2.nextSibling;
47656
47657             if (s == n1) {
47658                 p.insertBefore(n1, n2);
47659             } else if (n2 == n1.nextSibling) {
47660                 p.insertBefore(n2, n1);
47661             } else {
47662                 n1.parentNode.replaceChild(n2, n1);
47663                 p.insertBefore(n1, s);
47664             }
47665         }
47666     },
47667
47668     /**
47669      * Returns the current scroll position
47670      * @private
47671      */
47672     getScroll: function () {
47673         var doc   = window.document,
47674             docEl = doc.documentElement,
47675             body  = doc.body,
47676             top   = 0,
47677             left  = 0;
47678
47679         if (Ext.isGecko4) {
47680             top  = window.scrollYOffset;
47681             left = window.scrollXOffset;
47682         } else {
47683             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
47684                 top  = docEl.scrollTop;
47685                 left = docEl.scrollLeft;
47686             } else if (body) {
47687                 top  = body.scrollTop;
47688                 left = body.scrollLeft;
47689             }
47690         }
47691         return {
47692             top: top,
47693             left: left
47694         };
47695     },
47696
47697     /**
47698      * Returns the specified element style property
47699      * @param {HTMLElement} el          the element
47700      * @param {String}      styleProp   the style property
47701      * @return {String} The value of the style property
47702      */
47703     getStyle: function(el, styleProp) {
47704         return Ext.fly(el).getStyle(styleProp);
47705     },
47706
47707     /**
47708      * Gets the scrollTop
47709      * @return {Number} the document's scrollTop
47710      */
47711     getScrollTop: function () {
47712         return this.getScroll().top;
47713     },
47714
47715     /**
47716      * Gets the scrollLeft
47717      * @return {Number} the document's scrollTop
47718      */
47719     getScrollLeft: function () {
47720         return this.getScroll().left;
47721     },
47722
47723     /**
47724      * Sets the x/y position of an element to the location of the
47725      * target element.
47726      * @param {HTMLElement} moveEl      The element to move
47727      * @param {HTMLElement} targetEl    The position reference element
47728      */
47729     moveToEl: function (moveEl, targetEl) {
47730         var aCoord = Ext.Element.getXY(targetEl);
47731         Ext.Element.setXY(moveEl, aCoord);
47732     },
47733
47734     /**
47735      * Numeric array sort function
47736      * @param {Number} a
47737      * @param {Number} b
47738      * @returns {Number} positive, negative or 0
47739      */
47740     numericSort: function(a, b) {
47741         return (a - b);
47742     },
47743
47744     /**
47745      * Internal counter
47746      * @property {Number} _timeoutCount
47747      * @private
47748      */
47749     _timeoutCount: 0,
47750
47751     /**
47752      * Trying to make the load order less important.  Without this we get
47753      * an error if this file is loaded before the Event Utility.
47754      * @private
47755      */
47756     _addListeners: function() {
47757         if ( document ) {
47758             this._onLoad();
47759         } else {
47760             if (this._timeoutCount > 2000) {
47761             } else {
47762                 setTimeout(this._addListeners, 10);
47763                 if (document && document.body) {
47764                     this._timeoutCount += 1;
47765                 }
47766             }
47767         }
47768     },
47769
47770     /**
47771      * Recursively searches the immediate parent and all child nodes for
47772      * the handle element in order to determine wheter or not it was
47773      * clicked.
47774      * @param {HTMLElement} node the html element to inspect
47775      */
47776     handleWasClicked: function(node, id) {
47777         if (this.isHandle(id, node.id)) {
47778             return true;
47779         } else {
47780             // check to see if this is a text node child of the one we want
47781             var p = node.parentNode;
47782
47783             while (p) {
47784                 if (this.isHandle(id, p.id)) {
47785                     return true;
47786                 } else {
47787                     p = p.parentNode;
47788                 }
47789             }
47790         }
47791
47792         return false;
47793     }
47794 }, function() {
47795     this._addListeners();
47796 });
47797
47798 /**
47799  * @class Ext.layout.container.Box
47800  * @extends Ext.layout.container.Container
47801  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
47802  */
47803
47804 Ext.define('Ext.layout.container.Box', {
47805
47806     /* Begin Definitions */
47807
47808     alias: ['layout.box'],
47809     extend: 'Ext.layout.container.Container',
47810     alternateClassName: 'Ext.layout.BoxLayout',
47811
47812     requires: [
47813         'Ext.layout.container.boxOverflow.None',
47814         'Ext.layout.container.boxOverflow.Menu',
47815         'Ext.layout.container.boxOverflow.Scroller',
47816         'Ext.util.Format',
47817         'Ext.dd.DragDropManager'
47818     ],
47819
47820     /* End Definitions */
47821
47822     /**
47823      * @cfg {Boolean/Number/Object} animate
47824      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
47825      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
47826      * <p>May be set as a property at any time.</p>
47827      */
47828
47829     /**
47830      * @cfg {Object} defaultMargins
47831      * <p>If the individual contained items do not have a <tt>margins</tt>
47832      * property specified or margin specified via CSS, the default margins from this property will be
47833      * applied to each item.</p>
47834      * <br><p>This property may be specified as an object containing margins
47835      * to apply in the format:</p><pre><code>
47836 {
47837     top: (top margin),
47838     right: (right margin),
47839     bottom: (bottom margin),
47840     left: (left margin)
47841 }</code></pre>
47842      * <p>This property may also be specified as a string containing
47843      * space-separated, numeric margin values. The order of the sides associated
47844      * with each value matches the way CSS processes margin values:</p>
47845      * <div class="mdetail-params"><ul>
47846      * <li>If there is only one value, it applies to all sides.</li>
47847      * <li>If there are two values, the top and bottom borders are set to the
47848      * first value and the right and left are set to the second.</li>
47849      * <li>If there are three values, the top is set to the first value, the left
47850      * and right are set to the second, and the bottom is set to the third.</li>
47851      * <li>If there are four values, they apply to the top, right, bottom, and
47852      * left, respectively.</li>
47853      * </ul></div>
47854      */
47855     defaultMargins: {
47856         top: 0,
47857         right: 0,
47858         bottom: 0,
47859         left: 0
47860     },
47861
47862     /**
47863      * @cfg {String} padding
47864      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
47865      * <p>This property must be specified as a string containing
47866      * space-separated, numeric padding values. The order of the sides associated
47867      * with each value matches the way CSS processes padding values:</p>
47868      * <div class="mdetail-params"><ul>
47869      * <li>If there is only one value, it applies to all sides.</li>
47870      * <li>If there are two values, the top and bottom borders are set to the
47871      * first value and the right and left are set to the second.</li>
47872      * <li>If there are three values, the top is set to the first value, the left
47873      * and right are set to the second, and the bottom is set to the third.</li>
47874      * <li>If there are four values, they apply to the top, right, bottom, and
47875      * left, respectively.</li>
47876      * </ul></div>
47877      */
47878     padding: '0',
47879     // documented in subclasses
47880     pack: 'start',
47881
47882     /**
47883      * @cfg {String} pack
47884      * Controls how the child items of the container are packed together. Acceptable configuration values
47885      * for this property are:
47886      * <div class="mdetail-params"><ul>
47887      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
47888      * <b>left</b> side of container</div></li>
47889      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
47890      * <b>mid-width</b> of container</div></li>
47891      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
47892      * side of container</div></li>
47893      * </ul></div>
47894      */
47895     /**
47896      * @cfg {Number} flex
47897      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
47898      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
47899      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
47900      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
47901      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
47902      */
47903
47904     type: 'box',
47905     scrollOffset: 0,
47906     itemCls: Ext.baseCSSPrefix + 'box-item',
47907     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
47908     innerCls: Ext.baseCSSPrefix + 'box-inner',
47909
47910     bindToOwnerCtContainer: true,
47911
47912     // availableSpaceOffset is used to adjust the availableWidth, typically used
47913     // to reserve space for a scrollbar
47914     availableSpaceOffset: 0,
47915
47916     // whether or not to reserve the availableSpaceOffset in layout calculations
47917     reserveOffset: true,
47918
47919     /**
47920      * @cfg {Boolean} shrinkToFit
47921      * True (the default) to allow fixed size components to shrink (limited to their
47922      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
47923      * overflow.
47924      */
47925     shrinkToFit: true,
47926
47927     /**
47928      * @cfg {Boolean} clearInnerCtOnLayout
47929      */
47930     clearInnerCtOnLayout: false,
47931
47932     flexSortFn: function (a, b) {
47933         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
47934             infiniteValue = Infinity;
47935         a = a.component[maxParallelPrefix] || infiniteValue;
47936         b = b.component[maxParallelPrefix] || infiniteValue;
47937         // IE 6/7 Don't like Infinity - Infinity...
47938         if (!isFinite(a) && !isFinite(b)) {
47939             return false;
47940         }
47941         return a - b;
47942     },
47943
47944     // Sort into *descending* order.
47945     minSizeSortFn: function(a, b) {
47946         return b.available - a.available;
47947     },
47948
47949     constructor: function(config) {
47950         var me = this;
47951
47952         me.callParent(arguments);
47953
47954         // The sort function needs access to properties in this, so must be bound.
47955         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
47956
47957         me.initOverflowHandler();
47958     },
47959
47960     /**
47961      * @private
47962      * Returns the current size and positioning of the passed child item.
47963      * @param {Ext.Component} child The child Component to calculate the box for
47964      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47965      */
47966     getChildBox: function(child) {
47967         child = child.el || this.owner.getComponent(child).el;
47968         var size = child.getBox(false, true);
47969         return {
47970             left: size.left,
47971             top: size.top,
47972             width: size.width,
47973             height: size.height
47974         };
47975     },
47976
47977     /**
47978      * @private
47979      * Calculates the size and positioning of the passed child item.
47980      * @param {Ext.Component} child The child Component to calculate the box for
47981      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47982      */
47983     calculateChildBox: function(child) {
47984         var me = this,
47985             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
47986             ln = boxes.length,
47987             i = 0;
47988
47989         child = me.owner.getComponent(child);
47990         for (; i < ln; i++) {
47991             if (boxes[i].component === child) {
47992                 return boxes[i];
47993             }
47994         }
47995     },
47996
47997     /**
47998      * @private
47999      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
48000      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
48001      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
48002      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
48003      * @param {Object} targetSize Object containing target size and height
48004      * @return {Object} Object containing box measurements for each child, plus meta data
48005      */
48006     calculateChildBoxes: function(visibleItems, targetSize) {
48007         var me = this,
48008             math = Math,
48009             mmax = math.max,
48010             infiniteValue = Infinity,
48011             undefinedValue,
48012
48013             parallelPrefix = me.parallelPrefix,
48014             parallelPrefixCap = me.parallelPrefixCap,
48015             perpendicularPrefix = me.perpendicularPrefix,
48016             perpendicularPrefixCap = me.perpendicularPrefixCap,
48017             parallelMinString = 'min' + parallelPrefixCap,
48018             perpendicularMinString = 'min' + perpendicularPrefixCap,
48019             perpendicularMaxString = 'max' + perpendicularPrefixCap,
48020
48021             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
48022             perpendicularSize = targetSize[perpendicularPrefix],
48023             padding = me.padding,
48024             parallelOffset = padding[me.parallelBefore],
48025             paddingParallel = parallelOffset + padding[me.parallelAfter],
48026             perpendicularOffset = padding[me.perpendicularLeftTop],
48027             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
48028             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
48029
48030             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
48031
48032             isStart = me.pack == 'start',
48033             isCenter = me.pack == 'center',
48034             isEnd = me.pack == 'end',
48035
48036             constrain = Ext.Number.constrain,
48037             visibleCount = visibleItems.length,
48038             nonFlexSize = 0,
48039             totalFlex = 0,
48040             desiredSize = 0,
48041             minimumSize = 0,
48042             maxSize = 0,
48043             boxes = [],
48044             minSizes = [],
48045             calculatedWidth,
48046
48047             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
48048             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
48049             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
48050             perpendicularMargins, stretchSize;
48051
48052         //gather the total flex of all flexed items and the width taken up by fixed width items
48053         for (i = 0; i < visibleCount; i++) {
48054             child = visibleItems[i];
48055             childPerpendicular = child[perpendicularPrefix];
48056             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
48057                 if (child.componentLayout.initialized !== true) {
48058                     me.layoutItem(child);
48059                 }
48060             }
48061
48062             childMargins = child.margins;
48063             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
48064
48065             // Create the box description object for this child item.
48066             tmpObj = {
48067                 component: child,
48068                 margins: childMargins
48069             };
48070
48071             // flex and not 'auto' width
48072             if (child.flex) {
48073                 totalFlex += child.flex;
48074                 childParallel = undefinedValue;
48075             }
48076             // Not flexed or 'auto' width or undefined width
48077             else {
48078                 if (!(child[parallelPrefix] && childPerpendicular)) {
48079                     childSize = child.getSize();
48080                 }
48081                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
48082                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
48083             }
48084
48085             nonFlexSize += parallelMargins + (childParallel || 0);
48086             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
48087             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
48088
48089             // Max height for align - force layout of non-laid out subcontainers without a numeric height
48090             if (typeof childPerpendicular != 'number') {
48091                 // Clear any static sizing and revert to flow so we can get a proper measurement
48092                 // child['set' + perpendicularPrefixCap](null);
48093                 childPerpendicular = child['get' + perpendicularPrefixCap]();
48094             }
48095
48096             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
48097             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
48098             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
48099
48100             tmpObj[parallelPrefix] = childParallel || undefinedValue;
48101             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
48102             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
48103             boxes.push(tmpObj);
48104         }
48105
48106         // Only calculate parallel overflow indicators if we are not auto sizing
48107         if (!me.autoSize) {
48108             shortfall = desiredSize - parallelSize;
48109             tooNarrow = minimumSize > parallelSize;
48110         }
48111
48112         //the space available to the flexed items
48113         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
48114
48115         if (tooNarrow) {
48116             for (i = 0; i < visibleCount; i++) {
48117                 box = boxes[i];
48118                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
48119                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48120                 box[parallelPrefix] = minSize;
48121             }
48122         }
48123         else {
48124             //all flexed items should be sized to their minimum size, other items should be shrunk down until
48125             //the shortfall has been accounted for
48126             if (shortfall > 0) {
48127                 /*
48128                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
48129                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
48130                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
48131                  */
48132                 for (i = 0; i < visibleCount; i++) {
48133                     item = visibleItems[i];
48134                     minSize = item[parallelMinString] || 0;
48135
48136                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
48137                     //shrunk to their minSize because they're flexible and should be the first to lose size
48138                     if (item.flex) {
48139                         box = boxes[i];
48140                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48141                         box[parallelPrefix] = minSize;
48142                     } else if (me.shrinkToFit) {
48143                         minSizes.push({
48144                             minSize: minSize,
48145                             available: boxes[i][parallelPrefix] - minSize,
48146                             index: i
48147                         });
48148                     }
48149                 }
48150
48151                 //sort by descending amount of width remaining before minWidth is reached
48152                 Ext.Array.sort(minSizes, me.minSizeSortFn);
48153
48154                 /*
48155                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
48156                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
48157                  * smallest difference between their size and minSize first, so that if reducing the size by the average
48158                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
48159                  */
48160                 for (i = 0, length = minSizes.length; i < length; i++) {
48161                     itemIndex = minSizes[i].index;
48162
48163                     if (itemIndex == undefinedValue) {
48164                         continue;
48165                     }
48166                     item = visibleItems[itemIndex];
48167                     minSize = minSizes[i].minSize;
48168
48169                     box = boxes[itemIndex];
48170                     oldSize = box[parallelPrefix];
48171                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
48172                     reduction = oldSize - newSize;
48173
48174                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
48175                     box[parallelPrefix] = newSize;
48176                     shortfall -= reduction;
48177                 }
48178                 tooNarrow = (shortfall > 0);
48179             }
48180             else {
48181                 remainingSpace = availableSpace;
48182                 remainingFlex = totalFlex;
48183                 flexedBoxes = [];
48184
48185                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
48186                 for (i = 0; i < visibleCount; i++) {
48187                     child = visibleItems[i];
48188                     if (isStart && child.flex) {
48189                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
48190                     }
48191                 }
48192                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
48193                 // so that unallocated space caused by maxWidth being less than flexed width
48194                 // can be reallocated to subsequent flexed boxes.
48195                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
48196
48197                 // Calculate the size of each flexed item, and attempt to set it.
48198                 for (i = 0; i < flexedBoxes.length; i++) {
48199                     calcs = flexedBoxes[i];
48200                     child = calcs.component;
48201                     childMargins = calcs.margins;
48202
48203                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
48204
48205                     // Implement maxSize and minSize check
48206                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
48207
48208                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
48209                     remainingSpace -= flexedSize;
48210                     remainingFlex -= child.flex;
48211
48212                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
48213                     calcs[parallelPrefix] = flexedSize;
48214                 }
48215             }
48216         }
48217
48218         if (isCenter) {
48219             parallelOffset += availableSpace / 2;
48220         }
48221         else if (isEnd) {
48222             parallelOffset += availableSpace;
48223         }
48224
48225         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
48226         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
48227         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
48228         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
48229         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
48230
48231             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
48232             if (me.owner.frameSize) {
48233                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
48234             }
48235             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
48236             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
48237         }
48238
48239         //finally, calculate the left and top position of each item
48240         for (i = 0; i < visibleCount; i++) {
48241             child = visibleItems[i];
48242             calcs = boxes[i];
48243
48244             childMargins = calcs.margins;
48245
48246             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
48247
48248             // Advance past the "before" margin
48249             parallelOffset += childMargins[me.parallelBefore];
48250
48251             calcs[me.parallelBefore] = parallelOffset;
48252             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
48253
48254             if (me.align == 'stretch') {
48255                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48256                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48257                 calcs[perpendicularPrefix] = stretchSize;
48258             }
48259             else if (me.align == 'stretchmax') {
48260                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48261                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48262                 calcs[perpendicularPrefix] = stretchSize;
48263             }
48264             else if (me.align == me.alignCenteringString) {
48265                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
48266                 // the size to yield the space available to center within.
48267                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
48268                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
48269                 if (diff > 0) {
48270                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
48271                 }
48272             }
48273
48274             // Advance past the box size and the "after" margin
48275             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
48276         }
48277
48278         return {
48279             boxes: boxes,
48280             meta : {
48281                 calculatedWidth: calculatedWidth,
48282                 maxSize: maxSize,
48283                 nonFlexSize: nonFlexSize,
48284                 desiredSize: desiredSize,
48285                 minimumSize: minimumSize,
48286                 shortfall: shortfall,
48287                 tooNarrow: tooNarrow
48288             }
48289         };
48290     },
48291
48292     onRemove: function(comp){
48293         this.callParent(arguments);
48294         if (this.overflowHandler) {
48295             this.overflowHandler.onRemove(comp);
48296         }
48297     },
48298
48299     /**
48300      * @private
48301      */
48302     initOverflowHandler: function() {
48303         var handler = this.overflowHandler;
48304
48305         if (typeof handler == 'string') {
48306             handler = {
48307                 type: handler
48308             };
48309         }
48310
48311         var handlerType = 'None';
48312         if (handler && handler.type !== undefined) {
48313             handlerType = handler.type;
48314         }
48315
48316         var constructor = Ext.layout.container.boxOverflow[handlerType];
48317         if (constructor[this.type]) {
48318             constructor = constructor[this.type];
48319         }
48320
48321         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
48322     },
48323
48324     /**
48325      * @private
48326      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
48327      * when laying out
48328      */
48329     onLayout: function() {
48330         this.callParent();
48331         // Clear the innerCt size so it doesn't influence the child items.
48332         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
48333             this.innerCt.setSize(null, null);
48334         }
48335
48336         var me = this,
48337             targetSize = me.getLayoutTargetSize(),
48338             items = me.getVisibleItems(),
48339             calcs = me.calculateChildBoxes(items, targetSize),
48340             boxes = calcs.boxes,
48341             meta = calcs.meta,
48342             handler, method, results;
48343
48344         if (me.autoSize && calcs.meta.desiredSize) {
48345             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
48346         }
48347
48348         //invoke the overflow handler, if one is configured
48349         if (meta.shortfall > 0) {
48350             handler = me.overflowHandler;
48351             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
48352
48353             results = handler[method](calcs, targetSize);
48354
48355             if (results) {
48356                 if (results.targetSize) {
48357                     targetSize = results.targetSize;
48358                 }
48359
48360                 if (results.recalculate) {
48361                     items = me.getVisibleItems();
48362                     calcs = me.calculateChildBoxes(items, targetSize);
48363                     boxes = calcs.boxes;
48364                 }
48365             }
48366         } else {
48367             me.overflowHandler.clearOverflow();
48368         }
48369
48370         /**
48371          * @private
48372          * @property layoutTargetLastSize
48373          * @type Object
48374          * Private cache of the last measured size of the layout target. This should never be used except by
48375          * BoxLayout subclasses during their onLayout run.
48376          */
48377         me.layoutTargetLastSize = targetSize;
48378
48379         /**
48380          * @private
48381          * @property childBoxCache
48382          * @type Array
48383          * Array of the last calculated height, width, top and left positions of each visible rendered component
48384          * within the Box layout.
48385          */
48386         me.childBoxCache = calcs;
48387
48388         me.updateInnerCtSize(targetSize, calcs);
48389         me.updateChildBoxes(boxes);
48390         me.handleTargetOverflow(targetSize);
48391     },
48392     
48393     animCallback: Ext.emptyFn,
48394
48395     /**
48396      * Resizes and repositions each child component
48397      * @param {Object[]} boxes The box measurements
48398      */
48399     updateChildBoxes: function(boxes) {
48400         var me = this,
48401             i = 0,
48402             length = boxes.length,
48403             animQueue = [],
48404             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
48405             oldBox, newBox, changed, comp, boxAnim, animCallback;
48406
48407         for (; i < length; i++) {
48408             newBox = boxes[i];
48409             comp = newBox.component;
48410
48411             // If a Component is being drag/dropped, skip positioning it.
48412             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
48413             if (dd && (dd.getDragEl() === comp.el.dom)) {
48414                 continue;
48415             }
48416
48417             changed = false;
48418
48419             oldBox = me.getChildBox(comp);
48420
48421             // If we are animating, we build up an array of Anim config objects, one for each
48422             // child Component which has any changed box properties. Those with unchanged
48423             // properties are not animated.
48424             if (me.animate) {
48425                 // Animate may be a config object containing callback.
48426                 animCallback = me.animate.callback || me.animate;
48427                 boxAnim = {
48428                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
48429                     target: comp,
48430                     from: {},
48431                     to: {},
48432                     listeners: {}
48433                 };
48434                 // Only set from and to properties when there's a change.
48435                 // Perform as few Component setter methods as possible.
48436                 // Temporarily set the property values that we are not animating
48437                 // so that doComponentLayout does not auto-size them.
48438                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
48439                     changed = true;
48440                     // boxAnim.from.width = oldBox.width;
48441                     boxAnim.to.width = newBox.width;
48442                 }
48443                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
48444                     changed = true;
48445                     // boxAnim.from.height = oldBox.height;
48446                     boxAnim.to.height = newBox.height;
48447                 }
48448                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
48449                     changed = true;
48450                     // boxAnim.from.left = oldBox.left;
48451                     boxAnim.to.left = newBox.left;
48452                 }
48453                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
48454                     changed = true;
48455                     // boxAnim.from.top = oldBox.top;
48456                     boxAnim.to.top = newBox.top;
48457                 }
48458                 if (changed) {
48459                     animQueue.push(boxAnim);
48460                 }
48461             } else {
48462                 if (newBox.dirtySize) {
48463                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
48464                         me.setItemSize(comp, newBox.width, newBox.height);
48465                     }
48466                 }
48467                 // Don't set positions to NaN
48468                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
48469                     continue;
48470                 }
48471                 comp.setPosition(newBox.left, newBox.top);
48472             }
48473         }
48474
48475         // Kick off any queued animations
48476         length = animQueue.length;
48477         if (length) {
48478
48479             // A function which cleans up when a Component's animation is done.
48480             // The last one to finish calls the callback.
48481             var afterAnimate = function(anim) {
48482                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
48483                 length -= 1;
48484                 if (!length) {
48485                     me.animCallback(anim);
48486                     me.layoutBusy = false;
48487                     if (Ext.isFunction(animCallback)) {
48488                         animCallback();
48489                     }
48490                 }
48491             };
48492
48493             var beforeAnimate = function() {
48494                 me.layoutBusy = true;
48495             };
48496
48497             // Start each box animation off
48498             for (i = 0, length = animQueue.length; i < length; i++) {
48499                 boxAnim = animQueue[i];
48500
48501                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
48502                 boxAnim.listeners.afteranimate = afterAnimate;
48503
48504                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
48505                 if (!i) {
48506                     boxAnim.listeners.beforeanimate = beforeAnimate;
48507                 }
48508                 if (me.animate.duration) {
48509                     boxAnim.duration = me.animate.duration;
48510                 }
48511                 comp = boxAnim.target;
48512                 delete boxAnim.target;
48513                 // Stop any currently running animation
48514                 comp.stopAnimation();
48515                 comp.animate(boxAnim);
48516             }
48517         }
48518     },
48519
48520     /**
48521      * @private
48522      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
48523      * to make sure all child items fit within it. We call this before sizing the children because if our child
48524      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
48525      * again immediately afterwards, giving a performance hit.
48526      * Subclasses should provide an implementation.
48527      * @param {Object} currentSize The current height and width of the innerCt
48528      * @param {Object} calculations The new box calculations of all items to be laid out
48529      */
48530     updateInnerCtSize: function(tSize, calcs) {
48531         var me = this,
48532             mmax = Math.max,
48533             align = me.align,
48534             padding = me.padding,
48535             width = tSize.width,
48536             height = tSize.height,
48537             meta = calcs.meta,
48538             innerCtWidth,
48539             innerCtHeight;
48540
48541         if (me.direction == 'horizontal') {
48542             innerCtWidth = width;
48543             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
48544
48545             if (align == 'stretch') {
48546                 innerCtHeight = height;
48547             }
48548             else if (align == 'middle') {
48549                 innerCtHeight = mmax(height, innerCtHeight);
48550             }
48551         } else {
48552             innerCtHeight = height;
48553             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
48554
48555             if (align == 'stretch') {
48556                 innerCtWidth = width;
48557             }
48558             else if (align == 'center') {
48559                 innerCtWidth = mmax(width, innerCtWidth);
48560             }
48561         }
48562         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
48563
48564         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
48565         // then, if the Component has not assumed the size of its content, set it to do so.
48566         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
48567             me.owner.el.setWidth(meta.calculatedWidth);
48568         }
48569
48570         if (me.innerCt.dom.scrollTop) {
48571             me.innerCt.dom.scrollTop = 0;
48572         }
48573     },
48574
48575     /**
48576      * @private
48577      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
48578      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
48579      * target. Having a Box layout inside such a target is therefore not recommended.
48580      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
48581      * @param {Ext.container.Container} container The container
48582      * @param {Ext.Element} target The target element
48583      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
48584      */
48585     handleTargetOverflow: function(previousTargetSize) {
48586         var target = this.getTarget(),
48587             overflow = target.getStyle('overflow'),
48588             newTargetSize;
48589
48590         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
48591             newTargetSize = this.getLayoutTargetSize();
48592             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
48593                 this.adjustmentPass = true;
48594                 this.onLayout();
48595                 return true;
48596             }
48597         }
48598
48599         delete this.adjustmentPass;
48600     },
48601
48602     // private
48603     isValidParent : function(item, target, position) {
48604         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
48605         // We only care whether the item is a direct child of the innerCt element.
48606         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
48607         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
48608     },
48609
48610     // Overridden method from AbstractContainer.
48611     // Used in the base AbstractLayout.beforeLayout method to render all items into.
48612     getRenderTarget: function() {
48613         if (!this.innerCt) {
48614             // the innerCt prevents wrapping and shuffling while the container is resizing
48615             this.innerCt = this.getTarget().createChild({
48616                 cls: this.innerCls,
48617                 role: 'presentation'
48618             });
48619             this.padding = Ext.util.Format.parseBox(this.padding);
48620         }
48621         return this.innerCt;
48622     },
48623
48624     // private
48625     renderItem: function(item, target) {
48626         this.callParent(arguments);
48627         var me = this,
48628             itemEl = item.getEl(),
48629             style = itemEl.dom.style,
48630             margins = item.margins || item.margin;
48631
48632         // Parse the item's margin/margins specification
48633         if (margins) {
48634             if (Ext.isString(margins) || Ext.isNumber(margins)) {
48635                 margins = Ext.util.Format.parseBox(margins);
48636             } else {
48637                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
48638             }
48639         } else {
48640             margins = Ext.apply({}, me.defaultMargins);
48641         }
48642
48643         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
48644         margins.top    += itemEl.getMargin('t');
48645         margins.right  += itemEl.getMargin('r');
48646         margins.bottom += itemEl.getMargin('b');
48647         margins.left   += itemEl.getMargin('l');
48648         margins.height  = margins.top  + margins.bottom;
48649         margins.width   = margins.left + margins.right;
48650         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
48651
48652         // Item must reference calculated margins.
48653         item.margins = margins;
48654     },
48655
48656     /**
48657      * @private
48658      */
48659     destroy: function() {
48660         Ext.destroy(this.innerCt, this.overflowHandler);
48661         this.callParent(arguments);
48662     }
48663 });
48664 /**
48665  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
48666  * space between child items containing a numeric `flex` configuration.
48667  *
48668  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
48669  *
48670  *     @example
48671  *     Ext.create('Ext.Panel', {
48672  *         width: 500,
48673  *         height: 300,
48674  *         title: "HBoxLayout Panel",
48675  *         layout: {
48676  *             type: 'hbox',
48677  *             align: 'stretch'
48678  *         },
48679  *         renderTo: document.body,
48680  *         items: [{
48681  *             xtype: 'panel',
48682  *             title: 'Inner Panel One',
48683  *             flex: 2
48684  *         },{
48685  *             xtype: 'panel',
48686  *             title: 'Inner Panel Two',
48687  *             flex: 1
48688  *         },{
48689  *             xtype: 'panel',
48690  *             title: 'Inner Panel Three',
48691  *             flex: 1
48692  *         }]
48693  *     });
48694  */
48695 Ext.define('Ext.layout.container.HBox', {
48696
48697     /* Begin Definitions */
48698
48699     alias: ['layout.hbox'],
48700     extend: 'Ext.layout.container.Box',
48701     alternateClassName: 'Ext.layout.HBoxLayout',
48702
48703     /* End Definitions */
48704
48705     /**
48706      * @cfg {String} align
48707      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48708      *
48709      * - **top** : **Default** child items are aligned vertically at the **top** of the container
48710      * - **middle** : child items are aligned vertically in the **middle** of the container
48711      * - **stretch** : child items are stretched vertically to fill the height of the container
48712      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
48713      */
48714     align: 'top', // top, middle, stretch, strechmax
48715
48716     //@private
48717     alignCenteringString: 'middle',
48718
48719     type : 'hbox',
48720
48721     direction: 'horizontal',
48722
48723     // When creating an argument list to setSize, use this order
48724     parallelSizeIndex: 0,
48725     perpendicularSizeIndex: 1,
48726
48727     parallelPrefix: 'width',
48728     parallelPrefixCap: 'Width',
48729     parallelLT: 'l',
48730     parallelRB: 'r',
48731     parallelBefore: 'left',
48732     parallelBeforeCap: 'Left',
48733     parallelAfter: 'right',
48734     parallelPosition: 'x',
48735
48736     perpendicularPrefix: 'height',
48737     perpendicularPrefixCap: 'Height',
48738     perpendicularLT: 't',
48739     perpendicularRB: 'b',
48740     perpendicularLeftTop: 'top',
48741     perpendicularRightBottom: 'bottom',
48742     perpendicularPosition: 'y',
48743     configureItem: function(item) {
48744         if (item.flex) {
48745             item.layoutManagedWidth = 1;
48746         } else {
48747             item.layoutManagedWidth = 2;
48748         }
48749
48750         if (this.align === 'stretch' || this.align === 'stretchmax') {
48751             item.layoutManagedHeight = 1;
48752         } else {
48753             item.layoutManagedHeight = 2;
48754         }
48755         this.callParent(arguments);
48756     }
48757 });
48758 /**
48759  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
48760  * between child items containing a numeric `flex` configuration.
48761  *
48762  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
48763  *
48764  *     @example
48765  *     Ext.create('Ext.Panel', {
48766  *         width: 500,
48767  *         height: 400,
48768  *         title: "VBoxLayout Panel",
48769  *         layout: {
48770  *             type: 'vbox',
48771  *             align: 'center'
48772  *         },
48773  *         renderTo: document.body,
48774  *         items: [{
48775  *             xtype: 'panel',
48776  *             title: 'Inner Panel One',
48777  *             width: 250,
48778  *             flex: 2
48779  *         },
48780  *         {
48781  *             xtype: 'panel',
48782  *             title: 'Inner Panel Two',
48783  *             width: 250,
48784  *             flex: 4
48785  *         },
48786  *         {
48787  *             xtype: 'panel',
48788  *             title: 'Inner Panel Three',
48789  *             width: '50%',
48790  *             flex: 4
48791  *         }]
48792  *     });
48793  */
48794 Ext.define('Ext.layout.container.VBox', {
48795
48796     /* Begin Definitions */
48797
48798     alias: ['layout.vbox'],
48799     extend: 'Ext.layout.container.Box',
48800     alternateClassName: 'Ext.layout.VBoxLayout',
48801
48802     /* End Definitions */
48803
48804     /**
48805      * @cfg {String} align
48806      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48807      *
48808      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
48809      * - **center** : child items are aligned horizontally at the **mid-width** of the container
48810      * - **stretch** : child items are stretched horizontally to fill the width of the container
48811      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
48812      */
48813     align : 'left', // left, center, stretch, strechmax
48814
48815     //@private
48816     alignCenteringString: 'center',
48817
48818     type: 'vbox',
48819
48820     direction: 'vertical',
48821
48822     // When creating an argument list to setSize, use this order
48823     parallelSizeIndex: 1,
48824     perpendicularSizeIndex: 0,
48825
48826     parallelPrefix: 'height',
48827     parallelPrefixCap: 'Height',
48828     parallelLT: 't',
48829     parallelRB: 'b',
48830     parallelBefore: 'top',
48831     parallelBeforeCap: 'Top',
48832     parallelAfter: 'bottom',
48833     parallelPosition: 'y',
48834
48835     perpendicularPrefix: 'width',
48836     perpendicularPrefixCap: 'Width',
48837     perpendicularLT: 'l',
48838     perpendicularRB: 'r',
48839     perpendicularLeftTop: 'left',
48840     perpendicularRightBottom: 'right',
48841     perpendicularPosition: 'x',
48842     configureItem: function(item) {
48843         if (item.flex) {
48844             item.layoutManagedHeight = 1;
48845         } else {
48846             item.layoutManagedHeight = 2;
48847         }
48848
48849         if (this.align === 'stretch' || this.align === 'stretchmax') {
48850             item.layoutManagedWidth = 1;
48851         } else {
48852             item.layoutManagedWidth = 2;
48853         }
48854         this.callParent(arguments);
48855     }
48856 });
48857 /**
48858  * @class Ext.FocusManager
48859
48860 The FocusManager is responsible for globally:
48861
48862 1. Managing component focus
48863 2. Providing basic keyboard navigation
48864 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
48865
48866 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
48867 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
48868 FocusManager is disabled by default.
48869
48870 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
48871
48872 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
48873 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
48874 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
48875 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
48876
48877  * @singleton
48878  * @author Jarred Nicholls <jarred@sencha.com>
48879  * @docauthor Jarred Nicholls <jarred@sencha.com>
48880  */
48881 Ext.define('Ext.FocusManager', {
48882     singleton: true,
48883     alternateClassName: 'Ext.FocusMgr',
48884
48885     mixins: {
48886         observable: 'Ext.util.Observable'
48887     },
48888
48889     requires: [
48890         'Ext.ComponentManager',
48891         'Ext.ComponentQuery',
48892         'Ext.util.HashMap',
48893         'Ext.util.KeyNav'
48894     ],
48895
48896     /**
48897      * @property {Boolean} enabled
48898      * Whether or not the FocusManager is currently enabled
48899      */
48900     enabled: false,
48901
48902     /**
48903      * @property {Ext.Component} focusedCmp
48904      * The currently focused component. Defaults to `undefined`.
48905      */
48906
48907     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
48908
48909     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
48910
48911     /**
48912      * @property {String[]} whitelist
48913      * A list of xtypes that should ignore certain navigation input keys and
48914      * allow for the default browser event/behavior. These input keys include:
48915      *
48916      * 1. Backspace
48917      * 2. Delete
48918      * 3. Left
48919      * 4. Right
48920      * 5. Up
48921      * 6. Down
48922      *
48923      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
48924      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
48925      * the user to move the input cursor left and right, and to delete characters, etc.
48926      */
48927     whitelist: [
48928         'textfield'
48929     ],
48930
48931     tabIndexWhitelist: [
48932         'a',
48933         'button',
48934         'embed',
48935         'frame',
48936         'iframe',
48937         'img',
48938         'input',
48939         'object',
48940         'select',
48941         'textarea'
48942     ],
48943
48944     constructor: function() {
48945         var me = this,
48946             CQ = Ext.ComponentQuery;
48947
48948         me.addEvents(
48949             /**
48950              * @event beforecomponentfocus
48951              * Fires before a component becomes focused. Return `false` to prevent
48952              * the component from gaining focus.
48953              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48954              * @param {Ext.Component} cmp The component that is being focused
48955              * @param {Ext.Component} previousCmp The component that was previously focused,
48956              * or `undefined` if there was no previously focused component.
48957              */
48958             'beforecomponentfocus',
48959
48960             /**
48961              * @event componentfocus
48962              * Fires after a component becomes focused.
48963              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48964              * @param {Ext.Component} cmp The component that has been focused
48965              * @param {Ext.Component} previousCmp The component that was previously focused,
48966              * or `undefined` if there was no previously focused component.
48967              */
48968             'componentfocus',
48969
48970             /**
48971              * @event disable
48972              * Fires when the FocusManager is disabled
48973              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48974              */
48975             'disable',
48976
48977             /**
48978              * @event enable
48979              * Fires when the FocusManager is enabled
48980              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48981              */
48982             'enable'
48983         );
48984
48985         // Setup KeyNav that's bound to document to catch all
48986         // unhandled/bubbled key events for navigation
48987         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
48988             disabled: true,
48989             scope: me,
48990
48991             backspace: me.focusLast,
48992             enter: me.navigateIn,
48993             esc: me.navigateOut,
48994             tab: me.navigateSiblings
48995
48996             //space: me.navigateIn,
48997             //del: me.focusLast,
48998             //left: me.navigateSiblings,
48999             //right: me.navigateSiblings,
49000             //down: me.navigateSiblings,
49001             //up: me.navigateSiblings
49002         });
49003
49004         me.focusData = {};
49005         me.subscribers = Ext.create('Ext.util.HashMap');
49006         me.focusChain = {};
49007
49008         // Setup some ComponentQuery pseudos
49009         Ext.apply(CQ.pseudos, {
49010             focusable: function(cmps) {
49011                 var len = cmps.length,
49012                     results = [],
49013                     i = 0,
49014                     c,
49015
49016                     isFocusable = function(x) {
49017                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
49018                     };
49019
49020                 for (; i < len; i++) {
49021                     c = cmps[i];
49022                     if (isFocusable(c)) {
49023                         results.push(c);
49024                     }
49025                 }
49026
49027                 return results;
49028             },
49029
49030             nextFocus: function(cmps, idx, step) {
49031                 step = step || 1;
49032                 idx = parseInt(idx, 10);
49033
49034                 var len = cmps.length,
49035                     i = idx + step,
49036                     c;
49037
49038                 for (; i != idx; i += step) {
49039                     if (i >= len) {
49040                         i = 0;
49041                     } else if (i < 0) {
49042                         i = len - 1;
49043                     }
49044
49045                     c = cmps[i];
49046                     if (CQ.is(c, ':focusable')) {
49047                         return [c];
49048                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
49049                         return [c.placeholder];
49050                     }
49051                 }
49052
49053                 return [];
49054             },
49055
49056             prevFocus: function(cmps, idx) {
49057                 return this.nextFocus(cmps, idx, -1);
49058             },
49059
49060             root: function(cmps) {
49061                 var len = cmps.length,
49062                     results = [],
49063                     i = 0,
49064                     c;
49065
49066                 for (; i < len; i++) {
49067                     c = cmps[i];
49068                     if (!c.ownerCt) {
49069                         results.push(c);
49070                     }
49071                 }
49072
49073                 return results;
49074             }
49075         });
49076     },
49077
49078     /**
49079      * Adds the specified xtype to the {@link #whitelist}.
49080      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
49081      */
49082     addXTypeToWhitelist: function(xtype) {
49083         var me = this;
49084
49085         if (Ext.isArray(xtype)) {
49086             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
49087             return;
49088         }
49089
49090         if (!Ext.Array.contains(me.whitelist, xtype)) {
49091             me.whitelist.push(xtype);
49092         }
49093     },
49094
49095     clearComponent: function(cmp) {
49096         clearTimeout(this.cmpFocusDelay);
49097         if (!cmp.isDestroyed) {
49098             cmp.blur();
49099         }
49100     },
49101
49102     /**
49103      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
49104      */
49105     disable: function() {
49106         var me = this;
49107
49108         if (!me.enabled) {
49109             return;
49110         }
49111
49112         delete me.options;
49113         me.enabled = false;
49114
49115         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
49116
49117         me.removeDOM();
49118
49119         // Stop handling key navigation
49120         me.keyNav.disable();
49121
49122         // disable focus for all components
49123         me.setFocusAll(false);
49124
49125         me.fireEvent('disable', me);
49126     },
49127
49128     /**
49129      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
49130      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
49131         - focusFrame : Boolean
49132             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49133      * @markdown
49134      */
49135     enable: function(options) {
49136         var me = this;
49137
49138         if (options === true) {
49139             options = { focusFrame: true };
49140         }
49141         me.options = options = options || {};
49142
49143         if (me.enabled) {
49144             return;
49145         }
49146
49147         // Handle components that are newly added after we are enabled
49148         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
49149
49150         me.initDOM(options);
49151
49152         // Start handling key navigation
49153         me.keyNav.enable();
49154
49155         // enable focus for all components
49156         me.setFocusAll(true, options);
49157
49158         // Finally, let's focus our global focus el so we start fresh
49159         me.focusEl.focus();
49160         delete me.focusedCmp;
49161
49162         me.enabled = true;
49163         me.fireEvent('enable', me);
49164     },
49165
49166     focusLast: function(e) {
49167         var me = this;
49168
49169         if (me.isWhitelisted(me.focusedCmp)) {
49170             return true;
49171         }
49172
49173         // Go back to last focused item
49174         if (me.previousFocusedCmp) {
49175             me.previousFocusedCmp.focus();
49176         }
49177     },
49178
49179     getRootComponents: function() {
49180         var me = this,
49181             CQ = Ext.ComponentQuery,
49182             inline = CQ.query(':focusable:root:not([floating])'),
49183             floating = CQ.query(':focusable:root[floating]');
49184
49185         // Floating items should go to the top of our root stack, and be ordered
49186         // by their z-index (highest first)
49187         floating.sort(function(a, b) {
49188             return a.el.getZIndex() > b.el.getZIndex();
49189         });
49190
49191         return floating.concat(inline);
49192     },
49193
49194     initDOM: function(options) {
49195         var me = this,
49196             sp = '&#160',
49197             cls = me.focusFrameCls;
49198
49199         if (!Ext.isReady) {
49200             Ext.onReady(me.initDOM, me);
49201             return;
49202         }
49203
49204         // Create global focus element
49205         if (!me.focusEl) {
49206             me.focusEl = Ext.getBody().createChild({
49207                 tabIndex: '-1',
49208                 cls: me.focusElementCls,
49209                 html: sp
49210             });
49211         }
49212
49213         // Create global focus frame
49214         if (!me.focusFrame && options.focusFrame) {
49215             me.focusFrame = Ext.getBody().createChild({
49216                 cls: cls,
49217                 children: [
49218                     { cls: cls + '-top' },
49219                     { cls: cls + '-bottom' },
49220                     { cls: cls + '-left' },
49221                     { cls: cls + '-right' }
49222                 ],
49223                 style: 'top: -100px; left: -100px;'
49224             });
49225             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
49226             me.focusFrameWidth = 2;
49227             me.focusFrame.hide().setLeftTop(0, 0);
49228         }
49229     },
49230
49231     isWhitelisted: function(cmp) {
49232         return cmp && Ext.Array.some(this.whitelist, function(x) {
49233             return cmp.isXType(x);
49234         });
49235     },
49236
49237     navigateIn: function(e) {
49238         var me = this,
49239             focusedCmp = me.focusedCmp,
49240             rootCmps,
49241             firstChild;
49242
49243         if (!focusedCmp) {
49244             // No focus yet, so focus the first root cmp on the page
49245             rootCmps = me.getRootComponents();
49246             if (rootCmps.length) {
49247                 rootCmps[0].focus();
49248             }
49249         } else {
49250             // Drill into child ref items of the focused cmp, if applicable.
49251             // This works for any Component with a getRefItems implementation.
49252             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
49253             if (firstChild) {
49254                 firstChild.focus();
49255             } else {
49256                 // Let's try to fire a click event, as if it came from the mouse
49257                 if (Ext.isFunction(focusedCmp.onClick)) {
49258                     e.button = 0;
49259                     focusedCmp.onClick(e);
49260                     focusedCmp.focus();
49261                 }
49262             }
49263         }
49264     },
49265
49266     navigateOut: function(e) {
49267         var me = this,
49268             parent;
49269
49270         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
49271             me.focusEl.focus();
49272         } else {
49273             parent.focus();
49274         }
49275
49276         // In some browsers (Chrome) FocusManager can handle this before other
49277         // handlers. Ext Windows have their own Esc key handling, so we need to
49278         // return true here to allow the event to bubble.
49279         return true;
49280     },
49281
49282     navigateSiblings: function(e, source, parent) {
49283         var me = this,
49284             src = source || me,
49285             key = e.getKey(),
49286             EO = Ext.EventObject,
49287             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
49288             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
49289             nextSelector = goBack ? 'prev' : 'next',
49290             idx, next, focusedCmp;
49291
49292         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
49293         if (!focusedCmp && !parent) {
49294             return;
49295         }
49296
49297         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
49298             return true;
49299         }
49300
49301         parent = parent || focusedCmp.up();
49302         if (parent) {
49303             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
49304             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
49305             if (next && focusedCmp !== next) {
49306                 next.focus();
49307                 return next;
49308             }
49309         }
49310     },
49311
49312     onComponentBlur: function(cmp, e) {
49313         var me = this;
49314
49315         if (me.focusedCmp === cmp) {
49316             me.previousFocusedCmp = cmp;
49317             delete me.focusedCmp;
49318         }
49319
49320         if (me.focusFrame) {
49321             me.focusFrame.hide();
49322         }
49323     },
49324
49325     onComponentCreated: function(hash, id, cmp) {
49326         this.setFocus(cmp, true, this.options);
49327     },
49328
49329     onComponentDestroy: function(cmp) {
49330         this.setFocus(cmp, false);
49331     },
49332
49333     onComponentFocus: function(cmp, e) {
49334         var me = this,
49335             chain = me.focusChain;
49336
49337         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
49338             me.clearComponent(cmp);
49339
49340             // Check our focus chain, so we don't run into a never ending recursion
49341             // If we've attempted (unsuccessfully) to focus this component before,
49342             // then we're caught in a loop of child->parent->...->child and we
49343             // need to cut the loop off rather than feed into it.
49344             if (chain[cmp.id]) {
49345                 return;
49346             }
49347
49348             // Try to focus the parent instead
49349             var parent = cmp.up();
49350             if (parent) {
49351                 // Add component to our focus chain to detect infinite focus loop
49352                 // before we fire off an attempt to focus our parent.
49353                 // See the comments above.
49354                 chain[cmp.id] = true;
49355                 parent.focus();
49356             }
49357
49358             return;
49359         }
49360
49361         // Clear our focus chain when we have a focusable component
49362         me.focusChain = {};
49363
49364         // Defer focusing for 90ms so components can do a layout/positioning
49365         // and give us an ability to buffer focuses
49366         clearTimeout(me.cmpFocusDelay);
49367         if (arguments.length !== 2) {
49368             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
49369             return;
49370         }
49371
49372         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
49373             me.clearComponent(cmp);
49374             return;
49375         }
49376
49377         me.focusedCmp = cmp;
49378
49379         // If we have a focus frame, show it around the focused component
49380         if (me.shouldShowFocusFrame(cmp)) {
49381             var cls = '.' + me.focusFrameCls + '-',
49382                 ff = me.focusFrame,
49383                 fw = me.focusFrameWidth,
49384                 box = cmp.el.getPageBox(),
49385
49386             // Size the focus frame's t/b/l/r according to the box
49387             // This leaves a hole in the middle of the frame so user
49388             // interaction w/ the mouse can continue
49389                 bt = box.top,
49390                 bl = box.left,
49391                 bw = box.width,
49392                 bh = box.height,
49393                 ft = ff.child(cls + 'top'),
49394                 fb = ff.child(cls + 'bottom'),
49395                 fl = ff.child(cls + 'left'),
49396                 fr = ff.child(cls + 'right');
49397
49398             ft.setWidth(bw).setLeftTop(bl, bt);
49399             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
49400             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
49401             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
49402
49403             ff.show();
49404         }
49405
49406         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
49407     },
49408
49409     onComponentHide: function(cmp) {
49410         var me = this,
49411             CQ = Ext.ComponentQuery,
49412             cmpHadFocus = false,
49413             focusedCmp,
49414             parent;
49415
49416         if (me.focusedCmp) {
49417             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
49418             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
49419
49420             if (focusedCmp) {
49421                 me.clearComponent(focusedCmp);
49422             }
49423         }
49424
49425         me.clearComponent(cmp);
49426
49427         if (cmpHadFocus) {
49428             parent = CQ.query('^:focusable', cmp)[0];
49429             if (parent) {
49430                 parent.focus();
49431             }
49432         }
49433     },
49434
49435     removeDOM: function() {
49436         var me = this;
49437
49438         // If we are still enabled globally, or there are still subscribers
49439         // then we will halt here, since our DOM stuff is still being used
49440         if (me.enabled || me.subscribers.length) {
49441             return;
49442         }
49443
49444         Ext.destroy(
49445             me.focusEl,
49446             me.focusFrame
49447         );
49448         delete me.focusEl;
49449         delete me.focusFrame;
49450         delete me.focusFrameWidth;
49451     },
49452
49453     /**
49454      * Removes the specified xtype from the {@link #whitelist}.
49455      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
49456      */
49457     removeXTypeFromWhitelist: function(xtype) {
49458         var me = this;
49459
49460         if (Ext.isArray(xtype)) {
49461             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
49462             return;
49463         }
49464
49465         Ext.Array.remove(me.whitelist, xtype);
49466     },
49467
49468     setFocus: function(cmp, focusable, options) {
49469         var me = this,
49470             el, dom, data,
49471
49472             needsTabIndex = function(n) {
49473                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
49474                     && n.tabIndex <= 0;
49475             };
49476
49477         options = options || {};
49478
49479         // Come back and do this after the component is rendered
49480         if (!cmp.rendered) {
49481             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
49482             return;
49483         }
49484
49485         el = cmp.getFocusEl();
49486         dom = el.dom;
49487
49488         // Decorate the component's focus el for focus-ability
49489         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
49490             if (focusable) {
49491                 data = {
49492                     focusFrame: options.focusFrame
49493                 };
49494
49495                 // Only set -1 tabIndex if we need it
49496                 // inputs, buttons, and anchor tags do not need it,
49497                 // and neither does any DOM that has it set already
49498                 // programmatically or in markup.
49499                 if (needsTabIndex(dom)) {
49500                     data.tabIndex = dom.tabIndex;
49501                     dom.tabIndex = -1;
49502                 }
49503
49504                 el.on({
49505                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
49506                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
49507                     scope: me
49508                 });
49509                 cmp.on({
49510                     hide: me.onComponentHide,
49511                     close: me.onComponentHide,
49512                     beforedestroy: me.onComponentDestroy,
49513                     scope: me
49514                 });
49515
49516                 me.focusData[cmp.id] = data;
49517             } else {
49518                 data = me.focusData[cmp.id];
49519                 if ('tabIndex' in data) {
49520                     dom.tabIndex = data.tabIndex;
49521                 }
49522                 el.un('focus', data.focusFn, me);
49523                 el.un('blur', data.blurFn, me);
49524                 cmp.un('hide', me.onComponentHide, me);
49525                 cmp.un('close', me.onComponentHide, me);
49526                 cmp.un('beforedestroy', me.onComponentDestroy, me);
49527
49528                 delete me.focusData[cmp.id];
49529             }
49530         }
49531     },
49532
49533     setFocusAll: function(focusable, options) {
49534         var me = this,
49535             cmps = Ext.ComponentManager.all.getArray(),
49536             len = cmps.length,
49537             cmp,
49538             i = 0;
49539
49540         for (; i < len; i++) {
49541             me.setFocus(cmps[i], focusable, options);
49542         }
49543     },
49544
49545     setupSubscriberKeys: function(container, keys) {
49546         var me = this,
49547             el = container.getFocusEl(),
49548             scope = keys.scope,
49549             handlers = {
49550                 backspace: me.focusLast,
49551                 enter: me.navigateIn,
49552                 esc: me.navigateOut,
49553                 scope: me
49554             },
49555
49556             navSiblings = function(e) {
49557                 if (me.focusedCmp === container) {
49558                     // Root the sibling navigation to this container, so that we
49559                     // can automatically dive into the container, rather than forcing
49560                     // the user to hit the enter key to dive in.
49561                     return me.navigateSiblings(e, me, container);
49562                 } else {
49563                     return me.navigateSiblings(e);
49564                 }
49565             };
49566
49567         Ext.iterate(keys, function(key, cb) {
49568             handlers[key] = function(e) {
49569                 var ret = navSiblings(e);
49570
49571                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
49572                     return true;
49573                 }
49574
49575                 return ret;
49576             };
49577         }, me);
49578
49579         return Ext.create('Ext.util.KeyNav', el, handlers);
49580     },
49581
49582     shouldShowFocusFrame: function(cmp) {
49583         var me = this,
49584             opts = me.options || {};
49585
49586         if (!me.focusFrame || !cmp) {
49587             return false;
49588         }
49589
49590         // Global trumps
49591         if (opts.focusFrame) {
49592             return true;
49593         }
49594
49595         if (me.focusData[cmp.id].focusFrame) {
49596             return true;
49597         }
49598
49599         return false;
49600     },
49601
49602     /**
49603      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
49604      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
49605      * @param {Boolean/Object} options An object of the following options
49606      * @param {Array/Object} options.keys
49607      * An array containing the string names of navigation keys to be supported. The allowed values are:
49608      *
49609      *   - 'left'
49610      *   - 'right'
49611      *   - 'up'
49612      *   - 'down'
49613      *
49614      * 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.:
49615      *
49616      *     {
49617      *         left: this.onLeftKey,
49618      *         right: this.onRightKey,
49619      *         scope: this
49620      *     }
49621      *
49622      * @param {Boolean} options.focusFrame
49623      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49624      */
49625     subscribe: function(container, options) {
49626         var me = this,
49627             EA = Ext.Array,
49628             data = {},
49629             subs = me.subscribers,
49630
49631             // Recursively add focus ability as long as a descendent container isn't
49632             // itself subscribed to the FocusManager, or else we'd have unwanted side
49633             // effects for subscribing a descendent container twice.
49634             safeSetFocus = function(cmp) {
49635                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49636                     EA.forEach(cmp.query('>'), safeSetFocus);
49637                     me.setFocus(cmp, true, options);
49638                     cmp.on('add', data.onAdd, me);
49639                 } else if (!cmp.isContainer) {
49640                     me.setFocus(cmp, true, options);
49641                 }
49642             };
49643
49644         // We only accept containers
49645         if (!container || !container.isContainer) {
49646             return;
49647         }
49648
49649         if (!container.rendered) {
49650             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
49651             return;
49652         }
49653
49654         // Init the DOM, incase this is the first time it will be used
49655         me.initDOM(options);
49656
49657         // Create key navigation for subscriber based on keys option
49658         data.keyNav = me.setupSubscriberKeys(container, options.keys);
49659
49660         // We need to keep track of components being added to our subscriber
49661         // and any containers nested deeply within it (omg), so let's do that.
49662         // Components that are removed are globally handled.
49663         // Also keep track of destruction of our container for auto-unsubscribe.
49664         data.onAdd = function(ct, cmp, idx) {
49665             safeSetFocus(cmp);
49666         };
49667         container.on('beforedestroy', me.unsubscribe, me);
49668
49669         // Now we setup focusing abilities for the container and all its components
49670         safeSetFocus(container);
49671
49672         // Add to our subscribers list
49673         subs.add(container.id, data);
49674     },
49675
49676     /**
49677      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
49678      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
49679      */
49680     unsubscribe: function(container) {
49681         var me = this,
49682             EA = Ext.Array,
49683             subs = me.subscribers,
49684             data,
49685
49686             // Recursively remove focus ability as long as a descendent container isn't
49687             // itself subscribed to the FocusManager, or else we'd have unwanted side
49688             // effects for unsubscribing an ancestor container.
49689             safeSetFocus = function(cmp) {
49690                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49691                     EA.forEach(cmp.query('>'), safeSetFocus);
49692                     me.setFocus(cmp, false);
49693                     cmp.un('add', data.onAdd, me);
49694                 } else if (!cmp.isContainer) {
49695                     me.setFocus(cmp, false);
49696                 }
49697             };
49698
49699         if (!container || !subs.containsKey(container.id)) {
49700             return;
49701         }
49702
49703         data = subs.get(container.id);
49704         data.keyNav.destroy();
49705         container.un('beforedestroy', me.unsubscribe, me);
49706         subs.removeAtKey(container.id);
49707         safeSetFocus(container);
49708         me.removeDOM();
49709     }
49710 });
49711 /**
49712  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
49713  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
49714  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
49715  *
49716  * ## Some items have shortcut strings for creation:
49717  *
49718  * | Shortcut | xtype         | Class                         | Description
49719  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
49720  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
49721  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
49722  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
49723  *
49724  *     @example
49725  *     Ext.create('Ext.toolbar.Toolbar', {
49726  *         renderTo: document.body,
49727  *         width   : 500,
49728  *         items: [
49729  *             {
49730  *                 // xtype: 'button', // default for Toolbars
49731  *                 text: 'Button'
49732  *             },
49733  *             {
49734  *                 xtype: 'splitbutton',
49735  *                 text : 'Split Button'
49736  *             },
49737  *             // begin using the right-justified button container
49738  *             '->', // same as { xtype: 'tbfill' }
49739  *             {
49740  *                 xtype    : 'textfield',
49741  *                 name     : 'field1',
49742  *                 emptyText: 'enter search term'
49743  *             },
49744  *             // add a vertical separator bar between toolbar items
49745  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
49746  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
49747  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
49748  *             'text 2',
49749  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
49750  *             'text 3'
49751  *         ]
49752  *     });
49753  *
49754  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
49755  *
49756  *     @example
49757  *     Ext.create('Ext.toolbar.Toolbar', {
49758  *         renderTo: document.body,
49759  *         width   : 400,
49760  *         items: [
49761  *             {
49762  *                 text: 'Button'
49763  *             },
49764  *             {
49765  *                 xtype: 'splitbutton',
49766  *                 text : 'Split Button'
49767  *             },
49768  *             '->',
49769  *             {
49770  *                 xtype    : 'textfield',
49771  *                 name     : 'field1',
49772  *                 emptyText: 'enter search term'
49773  *             }
49774  *         ]
49775  *     });
49776  *
49777  * Example
49778  *
49779  *     @example
49780  *     var enableBtn = Ext.create('Ext.button.Button', {
49781  *         text    : 'Enable All Items',
49782  *         disabled: true,
49783  *         scope   : this,
49784  *         handler : function() {
49785  *             //disable the enable button and enable the disable button
49786  *             enableBtn.disable();
49787  *             disableBtn.enable();
49788  *
49789  *             //enable the toolbar
49790  *             toolbar.enable();
49791  *         }
49792  *     });
49793  *
49794  *     var disableBtn = Ext.create('Ext.button.Button', {
49795  *         text    : 'Disable All Items',
49796  *         scope   : this,
49797  *         handler : function() {
49798  *             //enable the enable button and disable button
49799  *             disableBtn.disable();
49800  *             enableBtn.enable();
49801  *
49802  *             //disable the toolbar
49803  *             toolbar.disable();
49804  *         }
49805  *     });
49806  *
49807  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49808  *         renderTo: document.body,
49809  *         width   : 400,
49810  *         margin  : '5 0 0 0',
49811  *         items   : [enableBtn, disableBtn]
49812  *     });
49813  *
49814  * 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
49815  * which remove all items within the toolbar.
49816  *
49817  *     @example
49818  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49819  *         renderTo: document.body,
49820  *         width   : 700,
49821  *         items: [
49822  *             {
49823  *                 text: 'Example Button'
49824  *             }
49825  *         ]
49826  *     });
49827  *
49828  *     var addedItems = [];
49829  *
49830  *     Ext.create('Ext.toolbar.Toolbar', {
49831  *         renderTo: document.body,
49832  *         width   : 700,
49833  *         margin  : '5 0 0 0',
49834  *         items   : [
49835  *             {
49836  *                 text   : 'Add a button',
49837  *                 scope  : this,
49838  *                 handler: function() {
49839  *                     var text = prompt('Please enter the text for your button:');
49840  *                     addedItems.push(toolbar.add({
49841  *                         text: text
49842  *                     }));
49843  *                 }
49844  *             },
49845  *             {
49846  *                 text   : 'Add a text item',
49847  *                 scope  : this,
49848  *                 handler: function() {
49849  *                     var text = prompt('Please enter the text for your item:');
49850  *                     addedItems.push(toolbar.add(text));
49851  *                 }
49852  *             },
49853  *             {
49854  *                 text   : 'Add a toolbar seperator',
49855  *                 scope  : this,
49856  *                 handler: function() {
49857  *                     addedItems.push(toolbar.add('-'));
49858  *                 }
49859  *             },
49860  *             {
49861  *                 text   : 'Add a toolbar spacer',
49862  *                 scope  : this,
49863  *                 handler: function() {
49864  *                     addedItems.push(toolbar.add('->'));
49865  *                 }
49866  *             },
49867  *             '->',
49868  *             {
49869  *                 text   : 'Remove last inserted item',
49870  *                 scope  : this,
49871  *                 handler: function() {
49872  *                     if (addedItems.length) {
49873  *                         toolbar.remove(addedItems.pop());
49874  *                     } else if (toolbar.items.length) {
49875  *                         toolbar.remove(toolbar.items.last());
49876  *                     } else {
49877  *                         alert('No items in the toolbar');
49878  *                     }
49879  *                 }
49880  *             },
49881  *             {
49882  *                 text   : 'Remove all items',
49883  *                 scope  : this,
49884  *                 handler: function() {
49885  *                     toolbar.removeAll();
49886  *                 }
49887  *             }
49888  *         ]
49889  *     });
49890  *
49891  * @constructor
49892  * Creates a new Toolbar
49893  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
49894  * @docauthor Robert Dougan <rob@sencha.com>
49895  */
49896 Ext.define('Ext.toolbar.Toolbar', {
49897     extend: 'Ext.container.Container',
49898     requires: [
49899         'Ext.toolbar.Fill',
49900         'Ext.layout.container.HBox',
49901         'Ext.layout.container.VBox',
49902         'Ext.FocusManager'
49903     ],
49904     uses: [
49905         'Ext.toolbar.Separator'
49906     ],
49907     alias: 'widget.toolbar',
49908     alternateClassName: 'Ext.Toolbar',
49909
49910     isToolbar: true,
49911     baseCls  : Ext.baseCSSPrefix + 'toolbar',
49912     ariaRole : 'toolbar',
49913
49914     defaultType: 'button',
49915
49916     /**
49917      * @cfg {Boolean} vertical
49918      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
49919      */
49920     vertical: false,
49921
49922     /**
49923      * @cfg {String/Object} layout
49924      * This class assigns a default layout (`layout: 'hbox'`).
49925      * Developers _may_ override this configuration option if another layout
49926      * is required (the constructor must be passed a configuration object in this
49927      * case instead of an array).
49928      * See {@link Ext.container.Container#layout} for additional information.
49929      */
49930
49931     /**
49932      * @cfg {Boolean} enableOverflow
49933      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
49934      * items which overflow the Toolbar's width.
49935      */
49936     enableOverflow: false,
49937
49938     /**
49939      * @cfg {String} menuTriggerCls
49940      * Configure the icon class of the overflow button.
49941      */
49942     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
49943     
49944     // private
49945     trackMenus: true,
49946
49947     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
49948
49949     initComponent: function() {
49950         var me = this,
49951             keys;
49952
49953         // check for simplified (old-style) overflow config:
49954         if (!me.layout && me.enableOverflow) {
49955             me.layout = { overflowHandler: 'Menu' };
49956         }
49957
49958         if (me.dock === 'right' || me.dock === 'left') {
49959             me.vertical = true;
49960         }
49961
49962         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
49963             type: me.layout
49964         } : me.layout || {}, {
49965             type: me.vertical ? 'vbox' : 'hbox',
49966             align: me.vertical ? 'stretchmax' : 'middle',
49967             clearInnerCtOnLayout: true
49968         });
49969
49970         if (me.vertical) {
49971             me.addClsWithUI('vertical');
49972         }
49973
49974         // @TODO: remove this hack and implement a more general solution
49975         if (me.ui === 'footer') {
49976             me.ignoreBorderManagement = true;
49977         }
49978
49979         me.callParent();
49980
49981         /**
49982          * @event overflowchange
49983          * Fires after the overflow state has changed.
49984          * @param {Object} c The Container
49985          * @param {Boolean} lastOverflow overflow state
49986          */
49987         me.addEvents('overflowchange');
49988
49989         // Subscribe to Ext.FocusManager for key navigation
49990         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
49991         Ext.FocusManager.subscribe(me, {
49992             keys: keys
49993         });
49994     },
49995
49996     getRefItems: function(deep) {
49997         var me = this,
49998             items = me.callParent(arguments),
49999             layout = me.layout,
50000             handler;
50001
50002         if (deep && me.enableOverflow) {
50003             handler = layout.overflowHandler;
50004             if (handler && handler.menu) {
50005                 items = items.concat(handler.menu.getRefItems(deep));
50006             }
50007         }
50008         return items;
50009     },
50010
50011     /**
50012      * Adds element(s) to the toolbar -- this function takes a variable number of
50013      * arguments of mixed type and adds them to the toolbar.
50014      *
50015      * **Note**: See the notes within {@link Ext.container.Container#add}.
50016      *
50017      * @param {Object...} args The following types of arguments are all valid:
50018      *  - `{@link Ext.button.Button config}`: A valid button config object
50019      *  - `HtmlElement`: Any standard HTML element
50020      *  - `Field`: Any form field
50021      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
50022      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
50023      *  Note that there are a few special strings that are treated differently as explained next.
50024      *  - `'-'`: Creates a separator element
50025      *  - `' '`: Creates a spacer element
50026      *  - `'->'`: Creates a fill element
50027      *
50028      * @method add
50029      */
50030
50031     // private
50032     lookupComponent: function(c) {
50033         if (Ext.isString(c)) {
50034             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
50035             if (shortcut) {
50036                 c = {
50037                     xtype: shortcut
50038                 };
50039             } else {
50040                 c = {
50041                     xtype: 'tbtext',
50042                     text: c
50043                 };
50044             }
50045             this.applyDefaults(c);
50046         }
50047         return this.callParent(arguments);
50048     },
50049
50050     // private
50051     applyDefaults: function(c) {
50052         if (!Ext.isString(c)) {
50053             c = this.callParent(arguments);
50054             var d = this.internalDefaults;
50055             if (c.events) {
50056                 Ext.applyIf(c.initialConfig, d);
50057                 Ext.apply(c, d);
50058             } else {
50059                 Ext.applyIf(c, d);
50060             }
50061         }
50062         return c;
50063     },
50064
50065     // private
50066     trackMenu: function(item, remove) {
50067         if (this.trackMenus && item.menu) {
50068             var method = remove ? 'mun' : 'mon',
50069                 me = this;
50070
50071             me[method](item, 'mouseover', me.onButtonOver, me);
50072             me[method](item, 'menushow', me.onButtonMenuShow, me);
50073             me[method](item, 'menuhide', me.onButtonMenuHide, me);
50074         }
50075     },
50076
50077     // private
50078     constructButton: function(item) {
50079         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
50080     },
50081
50082     // private
50083     onBeforeAdd: function(component) {
50084         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
50085             component.ui = component.ui + '-toolbar';
50086         }
50087
50088         // Any separators needs to know if is vertical or not
50089         if (component instanceof Ext.toolbar.Separator) {
50090             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
50091         }
50092
50093         this.callParent(arguments);
50094     },
50095
50096     // private
50097     onAdd: function(component) {
50098         this.callParent(arguments);
50099
50100         this.trackMenu(component);
50101         if (this.disabled) {
50102             component.disable();
50103         }
50104     },
50105
50106     // private
50107     onRemove: function(c) {
50108         this.callParent(arguments);
50109         this.trackMenu(c, true);
50110     },
50111
50112     // private
50113     onButtonOver: function(btn){
50114         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
50115             this.activeMenuBtn.hideMenu();
50116             btn.showMenu();
50117             this.activeMenuBtn = btn;
50118         }
50119     },
50120
50121     // private
50122     onButtonMenuShow: function(btn) {
50123         this.activeMenuBtn = btn;
50124     },
50125
50126     // private
50127     onButtonMenuHide: function(btn) {
50128         delete this.activeMenuBtn;
50129     }
50130 }, function() {
50131     this.shortcuts = {
50132         '-' : 'tbseparator',
50133         ' ' : 'tbspacer',
50134         '->': 'tbfill'
50135     };
50136 });
50137 /**
50138  * @class Ext.panel.AbstractPanel
50139  * @extends Ext.container.Container
50140  * A base class which provides methods common to Panel classes across the Sencha product range.
50141  * @private
50142  */
50143 Ext.define('Ext.panel.AbstractPanel', {
50144
50145     /* Begin Definitions */
50146
50147     extend: 'Ext.container.Container',
50148
50149     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
50150
50151     /* End Definitions */
50152
50153     /**
50154      * @cfg {String} [baseCls='x-panel']
50155      * The base CSS class to apply to this panel's element.
50156      */
50157     baseCls : Ext.baseCSSPrefix + 'panel',
50158
50159     /**
50160      * @cfg {Number/String} bodyPadding
50161      * A shortcut for setting a padding style on the body element. The value can either be
50162      * a number to be applied to all sides, or a normal css string describing padding.
50163      */
50164
50165     /**
50166      * @cfg {Boolean} bodyBorder
50167      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
50168      * which has the {@link #frame} configuration set to `true`.
50169      */
50170
50171     /**
50172      * @cfg {String/Object/Function} bodyStyle
50173      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
50174      * an object containing style property name/value pairs or a function that returns such a string or object.
50175      * For example, these two formats are interpreted to be equivalent:<pre><code>
50176 bodyStyle: 'background:#ffc; padding:10px;'
50177
50178 bodyStyle: {
50179     background: '#ffc',
50180     padding: '10px'
50181 }
50182      * </code></pre>
50183      */
50184
50185     /**
50186      * @cfg {String/String[]} bodyCls
50187      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
50188      * The following examples are all valid:<pre><code>
50189 bodyCls: 'foo'
50190 bodyCls: 'foo bar'
50191 bodyCls: ['foo', 'bar']
50192      * </code></pre>
50193      */
50194
50195     isPanel: true,
50196
50197     componentLayout: 'dock',
50198
50199     /**
50200      * @cfg {Object} defaultDockWeights
50201      * This object holds the default weights applied to dockedItems that have no weight. These start with a
50202      * weight of 1, to allow negative weights to insert before top items and are odd numbers
50203      * so that even weights can be used to get between different dock orders.
50204      *
50205      * To make default docking order match border layout, do this:
50206      * <pre><code>
50207 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
50208      * Changing these defaults as above or individually on this object will effect all Panels.
50209      * To change the defaults on a single panel, you should replace the entire object:
50210      * <pre><code>
50211 initComponent: function () {
50212     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50213     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
50214
50215     this.callParent();
50216 }</code></pre>
50217      *
50218      * To change only one of the default values, you do this:
50219      * <pre><code>
50220 initComponent: function () {
50221     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50222     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
50223
50224     this.callParent();
50225 }</code></pre>
50226      */
50227     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
50228
50229     renderTpl: [
50230         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50231             ' {baseCls}-body-{ui}<tpl if="uiCls">',
50232                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50233             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
50234         '</div>'
50235     ],
50236
50237     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
50238     /**
50239      * @cfg {Object/Object[]} dockedItems
50240      * A component or series of components to be added as docked items to this panel.
50241      * The docked items can be docked to either the top, right, left or bottom of a panel.
50242      * This is typically used for things like toolbars or tab bars:
50243      * <pre><code>
50244 var panel = new Ext.panel.Panel({
50245     fullscreen: true,
50246     dockedItems: [{
50247         xtype: 'toolbar',
50248         dock: 'top',
50249         items: [{
50250             text: 'Docked to the top'
50251         }]
50252     }]
50253 });</code></pre>
50254      */
50255
50256     border: true,
50257
50258     initComponent : function() {
50259         var me = this;
50260
50261         me.addEvents(
50262             /**
50263              * @event bodyresize
50264              * Fires after the Panel has been resized.
50265              * @param {Ext.panel.Panel} p the Panel which has been resized.
50266              * @param {Number} width The Panel body's new width.
50267              * @param {Number} height The Panel body's new height.
50268              */
50269             'bodyresize'
50270             // // inherited
50271             // 'activate',
50272             // // inherited
50273             // 'deactivate'
50274         );
50275
50276         me.addChildEls('body');
50277
50278         //!frame
50279         //!border
50280
50281         if (me.frame && me.border && me.bodyBorder === undefined) {
50282             me.bodyBorder = false;
50283         }
50284         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
50285             me.manageBodyBorders = true;
50286         }
50287
50288         me.callParent();
50289     },
50290
50291     // @private
50292     initItems : function() {
50293         var me = this,
50294             items = me.dockedItems;
50295
50296         me.callParent();
50297         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
50298         if (items) {
50299             me.addDocked(items);
50300         }
50301     },
50302
50303     /**
50304      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
50305      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
50306      * @return {Ext.Component} The docked component (if found)
50307      */
50308     getDockedComponent: function(comp) {
50309         if (Ext.isObject(comp)) {
50310             comp = comp.getItemId();
50311         }
50312         return this.dockedItems.get(comp);
50313     },
50314
50315     /**
50316      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
50317      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
50318      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
50319      * @param {String/Number} comp The component id, itemId or position to find
50320      * @return {Ext.Component} The component (if found)
50321      */
50322     getComponent: function(comp) {
50323         var component = this.callParent(arguments);
50324         if (component === undefined && !Ext.isNumber(comp)) {
50325             // If the arg is a numeric index skip docked items
50326             component = this.getDockedComponent(comp);
50327         }
50328         return component;
50329     },
50330
50331     /**
50332      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
50333      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
50334      * @return {String} A CSS style string with body styles, padding and border.
50335      * @private
50336      */
50337     initBodyStyles: function() {
50338         var me = this,
50339             bodyStyle = me.bodyStyle,
50340             styles = [],
50341             Element = Ext.Element,
50342             prop;
50343
50344         if (Ext.isFunction(bodyStyle)) {
50345             bodyStyle = bodyStyle();
50346         }
50347         if (Ext.isString(bodyStyle)) {
50348             styles = bodyStyle.split(';');
50349         } else {
50350             for (prop in bodyStyle) {
50351                 if (bodyStyle.hasOwnProperty(prop)) {
50352                     styles.push(prop + ':' + bodyStyle[prop]);
50353                 }
50354             }
50355         }
50356
50357         if (me.bodyPadding !== undefined) {
50358             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
50359         }
50360         if (me.frame && me.bodyBorder) {
50361             if (!Ext.isNumber(me.bodyBorder)) {
50362                 me.bodyBorder = 1;
50363             }
50364             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
50365         }
50366         delete me.bodyStyle;
50367         return styles.length ? styles.join(';') : undefined;
50368     },
50369
50370     /**
50371      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
50372      * CSS classes to be applied to the body element.
50373      * @return {String} The CSS class(es)
50374      * @private
50375      */
50376     initBodyCls: function() {
50377         var me = this,
50378             cls = '',
50379             bodyCls = me.bodyCls;
50380
50381         if (bodyCls) {
50382             Ext.each(bodyCls, function(v) {
50383                 cls += " " + v;
50384             });
50385             delete me.bodyCls;
50386         }
50387         return cls.length > 0 ? cls : undefined;
50388     },
50389
50390     /**
50391      * Initialized the renderData to be used when rendering the renderTpl.
50392      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
50393      * @private
50394      */
50395     initRenderData: function() {
50396         return Ext.applyIf(this.callParent(), {
50397             bodyStyle: this.initBodyStyles(),
50398             bodyCls: this.initBodyCls()
50399         });
50400     },
50401
50402     /**
50403      * Adds docked item(s) to the panel.
50404      * @param {Object/Object[]} component The Component or array of components to add. The components
50405      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
50406      * 'bottom', 'left').
50407      * @param {Number} pos (optional) The index at which the Component will be added
50408      */
50409     addDocked : function(items, pos) {
50410         var me = this,
50411             i = 0,
50412             item, length;
50413
50414         items = me.prepareItems(items);
50415         length = items.length;
50416
50417         for (; i < length; i++) {
50418             item = items[i];
50419             item.dock = item.dock || 'top';
50420
50421             // Allow older browsers to target docked items to style without borders
50422             if (me.border === false) {
50423                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
50424             }
50425
50426             if (pos !== undefined) {
50427                 me.dockedItems.insert(pos + i, item);
50428             }
50429             else {
50430                 me.dockedItems.add(item);
50431             }
50432             item.onAdded(me, i);
50433             me.onDockedAdd(item);
50434         }
50435
50436         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50437         me.componentLayout.childrenChanged = true;
50438         if (me.rendered && !me.suspendLayout) {
50439             me.doComponentLayout();
50440         }
50441         return items;
50442     },
50443
50444     // Placeholder empty functions
50445     onDockedAdd : Ext.emptyFn,
50446     onDockedRemove : Ext.emptyFn,
50447
50448     /**
50449      * Inserts docked item(s) to the panel at the indicated position.
50450      * @param {Number} pos The index at which the Component will be inserted
50451      * @param {Object/Object[]} component. The Component or array of components to add. The components
50452      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
50453      * 'bottom', 'left').
50454      */
50455     insertDocked : function(pos, items) {
50456         this.addDocked(items, pos);
50457     },
50458
50459     /**
50460      * Removes the docked item from the panel.
50461      * @param {Ext.Component} item. The Component to remove.
50462      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
50463      */
50464     removeDocked : function(item, autoDestroy) {
50465         var me = this,
50466             layout,
50467             hasLayout;
50468
50469         if (!me.dockedItems.contains(item)) {
50470             return item;
50471         }
50472
50473         layout = me.componentLayout;
50474         hasLayout = layout && me.rendered;
50475
50476         if (hasLayout) {
50477             layout.onRemove(item);
50478         }
50479
50480         me.dockedItems.remove(item);
50481         item.onRemoved();
50482         me.onDockedRemove(item);
50483
50484         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
50485             item.destroy();
50486         } else if (hasLayout) {
50487             // not destroying, make any layout related removals
50488             layout.afterRemove(item);    
50489         }
50490
50491
50492         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50493         me.componentLayout.childrenChanged = true;
50494         if (!me.destroying && !me.suspendLayout) {
50495             me.doComponentLayout();
50496         }
50497
50498         return item;
50499     },
50500
50501     /**
50502      * Retrieve an array of all currently docked Components.
50503      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
50504      * @return {Ext.Component[]} An array of components.
50505      */
50506     getDockedItems : function(cqSelector) {
50507         var me = this,
50508             defaultWeight = me.defaultDockWeights,
50509             dockedItems;
50510
50511         if (me.dockedItems && me.dockedItems.items.length) {
50512             // Allow filtering of returned docked items by CQ selector.
50513             if (cqSelector) {
50514                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
50515             } else {
50516                 dockedItems = me.dockedItems.items.slice();
50517             }
50518
50519             Ext.Array.sort(dockedItems, function(a, b) {
50520                 // Docked items are ordered by their visual representation by default (t,l,r,b)
50521                 var aw = a.weight || defaultWeight[a.dock],
50522                     bw = b.weight || defaultWeight[b.dock];
50523                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
50524                     return aw - bw;
50525                 }
50526                 return 0;
50527             });
50528
50529             return dockedItems;
50530         }
50531         return [];
50532     },
50533
50534     // inherit docs
50535     addUIClsToElement: function(cls, force) {
50536         var me = this,
50537             result = me.callParent(arguments),
50538             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50539             array, i;
50540
50541         if (!force && me.rendered) {
50542             if (me.bodyCls) {
50543                 me.body.addCls(me.bodyCls);
50544             } else {
50545                 me.body.addCls(classes);
50546             }
50547         } else {
50548             if (me.bodyCls) {
50549                 array = me.bodyCls.split(' ');
50550
50551                 for (i = 0; i < classes.length; i++) {
50552                     if (!Ext.Array.contains(array, classes[i])) {
50553                         array.push(classes[i]);
50554                     }
50555                 }
50556
50557                 me.bodyCls = array.join(' ');
50558             } else {
50559                 me.bodyCls = classes.join(' ');
50560             }
50561         }
50562
50563         return result;
50564     },
50565
50566     // inherit docs
50567     removeUIClsFromElement: function(cls, force) {
50568         var me = this,
50569             result = me.callParent(arguments),
50570             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50571             array, i;
50572
50573         if (!force && me.rendered) {
50574             if (me.bodyCls) {
50575                 me.body.removeCls(me.bodyCls);
50576             } else {
50577                 me.body.removeCls(classes);
50578             }
50579         } else {
50580             if (me.bodyCls) {
50581                 array = me.bodyCls.split(' ');
50582
50583                 for (i = 0; i < classes.length; i++) {
50584                     Ext.Array.remove(array, classes[i]);
50585                 }
50586
50587                 me.bodyCls = array.join(' ');
50588             }
50589         }
50590
50591         return result;
50592     },
50593
50594     // inherit docs
50595     addUIToElement: function(force) {
50596         var me = this,
50597             cls = me.baseCls + '-body-' + me.ui,
50598             array;
50599
50600         me.callParent(arguments);
50601
50602         if (!force && me.rendered) {
50603             if (me.bodyCls) {
50604                 me.body.addCls(me.bodyCls);
50605             } else {
50606                 me.body.addCls(cls);
50607             }
50608         } else {
50609             if (me.bodyCls) {
50610                 array = me.bodyCls.split(' ');
50611
50612                 if (!Ext.Array.contains(array, cls)) {
50613                     array.push(cls);
50614                 }
50615
50616                 me.bodyCls = array.join(' ');
50617             } else {
50618                 me.bodyCls = cls;
50619             }
50620         }
50621     },
50622
50623     // inherit docs
50624     removeUIFromElement: function() {
50625         var me = this,
50626             cls = me.baseCls + '-body-' + me.ui,
50627             array;
50628
50629         me.callParent(arguments);
50630
50631         if (me.rendered) {
50632             if (me.bodyCls) {
50633                 me.body.removeCls(me.bodyCls);
50634             } else {
50635                 me.body.removeCls(cls);
50636             }
50637         } else {
50638             if (me.bodyCls) {
50639                 array = me.bodyCls.split(' ');
50640                 Ext.Array.remove(array, cls);
50641                 me.bodyCls = array.join(' ');
50642             } else {
50643                 me.bodyCls = cls;
50644             }
50645         }
50646     },
50647
50648     // @private
50649     getTargetEl : function() {
50650         return this.body;
50651     },
50652
50653     getRefItems: function(deep) {
50654         var items = this.callParent(arguments),
50655             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
50656             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
50657             ln = dockedItems.length,
50658             i = 0,
50659             item;
50660
50661         // Find the index where we go from top/left docked items to right/bottom docked items
50662         for (; i < ln; i++) {
50663             item = dockedItems[i];
50664             if (item.dock === 'right' || item.dock === 'bottom') {
50665                 break;
50666             }
50667         }
50668
50669         // Return docked items in the top/left position before our container items, and
50670         // return right/bottom positioned items after our container items.
50671         // See AbstractDock.renderItems() for more information.
50672         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
50673     },
50674
50675     beforeDestroy: function(){
50676         var docked = this.dockedItems,
50677             c;
50678
50679         if (docked) {
50680             while ((c = docked.first())) {
50681                 this.removeDocked(c, true);
50682             }
50683         }
50684         this.callParent();
50685     },
50686
50687     setBorder: function(border) {
50688         var me = this;
50689         me.border = (border !== undefined) ? border : true;
50690         if (me.rendered) {
50691             me.doComponentLayout();
50692         }
50693     }
50694 });
50695 /**
50696  * @class Ext.panel.Header
50697  * @extends Ext.container.Container
50698  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
50699  */
50700 Ext.define('Ext.panel.Header', {
50701     extend: 'Ext.container.Container',
50702     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
50703     alias: 'widget.header',
50704
50705     isHeader       : true,
50706     defaultType    : 'tool',
50707     indicateDrag   : false,
50708     weight         : -1,
50709
50710     renderTpl: [
50711         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50712         '<tpl if="uiCls">',
50713             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50714         '</tpl>"',
50715         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
50716
50717     /**
50718      * @cfg {String} title
50719      * The title text to display
50720      */
50721
50722     /**
50723      * @cfg {String} iconCls
50724      * CSS class for icon in header. Used for displaying an icon to the left of a title.
50725      */
50726
50727     initComponent: function() {
50728         var me = this,
50729             ruleStyle,
50730             rule,
50731             style,
50732             titleTextEl,
50733             ui;
50734
50735         me.indicateDragCls = me.baseCls + '-draggable';
50736         me.title = me.title || '&#160;';
50737         me.tools = me.tools || [];
50738         me.items = me.items || [];
50739         me.orientation = me.orientation || 'horizontal';
50740         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
50741
50742         //add the dock as a ui
50743         //this is so we support top/right/left/bottom headers
50744         me.addClsWithUI(me.orientation);
50745         me.addClsWithUI(me.dock);
50746
50747         me.addChildEls('body');
50748
50749         // Add Icon
50750         if (!Ext.isEmpty(me.iconCls)) {
50751             me.initIconCmp();
50752             me.items.push(me.iconCmp);
50753         }
50754
50755         // Add Title
50756         if (me.orientation == 'vertical') {
50757             // Hack for IE6/7's inability to display an inline-block
50758             if (Ext.isIE6 || Ext.isIE7) {
50759                 me.width = this.width || 24;
50760             } else if (Ext.isIEQuirks) {
50761                 me.width = this.width || 25;
50762             }
50763
50764             me.layout = {
50765                 type : 'vbox',
50766                 align: 'center',
50767                 clearInnerCtOnLayout: true,
50768                 bindToOwnerCtContainer: false
50769             };
50770             me.textConfig = {
50771                 cls: me.baseCls + '-text',
50772                 type: 'text',
50773                 text: me.title,
50774                 rotate: {
50775                     degrees: 90
50776                 }
50777             };
50778             ui = me.ui;
50779             if (Ext.isArray(ui)) {
50780                 ui = ui[0];
50781             }
50782             ruleStyle = '.' + me.baseCls + '-text-' + ui;
50783             if (Ext.scopeResetCSS) {
50784                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
50785             }
50786             rule = Ext.util.CSS.getRule(ruleStyle);
50787             if (rule) {
50788                 style = rule.style;
50789             }
50790             if (style) {
50791                 Ext.apply(me.textConfig, {
50792                     'font-family': style.fontFamily,
50793                     'font-weight': style.fontWeight,
50794                     'font-size': style.fontSize,
50795                     fill: style.color
50796                 });
50797             }
50798             me.titleCmp = Ext.create('Ext.draw.Component', {
50799                 ariaRole  : 'heading',
50800                 focusable: false,
50801                 viewBox: false,
50802                 flex : 1,
50803                 autoSize: true,
50804                 margins: '5 0 0 0',
50805                 items: [ me.textConfig ],
50806                 // this is a bit of a cheat: we are not selecting an element of titleCmp
50807                 // but rather of titleCmp.items[0] (so we cannot use childEls)
50808                 renderSelectors: {
50809                     textEl: '.' + me.baseCls + '-text'
50810                 }
50811             });
50812         } else {
50813             me.layout = {
50814                 type : 'hbox',
50815                 align: 'middle',
50816                 clearInnerCtOnLayout: true,
50817                 bindToOwnerCtContainer: false
50818             };
50819             me.titleCmp = Ext.create('Ext.Component', {
50820                 xtype     : 'component',
50821                 ariaRole  : 'heading',
50822                 focusable: false,
50823                 flex : 1,
50824                 cls: me.baseCls + '-text-container',
50825                 renderTpl : [
50826                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
50827                 ],
50828                 renderData: {
50829                     title: me.title,
50830                     cls  : me.baseCls,
50831                     ui   : me.ui
50832                 },
50833                 childEls: ['textEl']
50834             });
50835         }
50836         me.items.push(me.titleCmp);
50837
50838         // Add Tools
50839         me.items = me.items.concat(me.tools);
50840         this.callParent();
50841     },
50842
50843     initIconCmp: function() {
50844         this.iconCmp = Ext.create('Ext.Component', {
50845             focusable: false,
50846             renderTpl : [
50847                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
50848             ],
50849             renderData: {
50850                 blank  : Ext.BLANK_IMAGE_URL,
50851                 cls    : this.baseCls,
50852                 iconCls: this.iconCls,
50853                 orientation: this.orientation
50854             },
50855             childEls: ['iconEl'],
50856             iconCls: this.iconCls
50857         });
50858     },
50859
50860     afterRender: function() {
50861         var me = this;
50862
50863         me.el.unselectable();
50864         if (me.indicateDrag) {
50865             me.el.addCls(me.indicateDragCls);
50866         }
50867         me.mon(me.el, {
50868             click: me.onClick,
50869             scope: me
50870         });
50871         me.callParent();
50872     },
50873
50874     afterLayout: function() {
50875         var me = this;
50876         me.callParent(arguments);
50877
50878         // IE7 needs a forced repaint to make the top framing div expand to full width
50879         if (Ext.isIE7) {
50880             me.el.repaint();
50881         }
50882     },
50883
50884     // inherit docs
50885     addUIClsToElement: function(cls, force) {
50886         var me = this,
50887             result = me.callParent(arguments),
50888             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50889             array, i;
50890
50891         if (!force && me.rendered) {
50892             if (me.bodyCls) {
50893                 me.body.addCls(me.bodyCls);
50894             } else {
50895                 me.body.addCls(classes);
50896             }
50897         } else {
50898             if (me.bodyCls) {
50899                 array = me.bodyCls.split(' ');
50900
50901                 for (i = 0; i < classes.length; i++) {
50902                     if (!Ext.Array.contains(array, classes[i])) {
50903                         array.push(classes[i]);
50904                     }
50905                 }
50906
50907                 me.bodyCls = array.join(' ');
50908             } else {
50909                 me.bodyCls = classes.join(' ');
50910             }
50911         }
50912
50913         return result;
50914     },
50915
50916     // inherit docs
50917     removeUIClsFromElement: function(cls, force) {
50918         var me = this,
50919             result = me.callParent(arguments),
50920             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50921             array, i;
50922
50923         if (!force && me.rendered) {
50924             if (me.bodyCls) {
50925                 me.body.removeCls(me.bodyCls);
50926             } else {
50927                 me.body.removeCls(classes);
50928             }
50929         } else {
50930             if (me.bodyCls) {
50931                 array = me.bodyCls.split(' ');
50932
50933                 for (i = 0; i < classes.length; i++) {
50934                     Ext.Array.remove(array, classes[i]);
50935                 }
50936
50937                 me.bodyCls = array.join(' ');
50938             }
50939         }
50940
50941        return result;
50942     },
50943
50944     // inherit docs
50945     addUIToElement: function(force) {
50946         var me = this,
50947             array, cls;
50948
50949         me.callParent(arguments);
50950
50951         cls = me.baseCls + '-body-' + me.ui;
50952         if (!force && me.rendered) {
50953             if (me.bodyCls) {
50954                 me.body.addCls(me.bodyCls);
50955             } else {
50956                 me.body.addCls(cls);
50957             }
50958         } else {
50959             if (me.bodyCls) {
50960                 array = me.bodyCls.split(' ');
50961
50962                 if (!Ext.Array.contains(array, cls)) {
50963                     array.push(cls);
50964                 }
50965
50966                 me.bodyCls = array.join(' ');
50967             } else {
50968                 me.bodyCls = cls;
50969             }
50970         }
50971
50972         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
50973             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
50974         }
50975     },
50976
50977     // inherit docs
50978     removeUIFromElement: function() {
50979         var me = this,
50980             array, cls;
50981
50982         me.callParent(arguments);
50983
50984         cls = me.baseCls + '-body-' + me.ui;
50985         if (me.rendered) {
50986             if (me.bodyCls) {
50987                 me.body.removeCls(me.bodyCls);
50988             } else {
50989                 me.body.removeCls(cls);
50990             }
50991         } else {
50992             if (me.bodyCls) {
50993                 array = me.bodyCls.split(' ');
50994                 Ext.Array.remove(array, cls);
50995                 me.bodyCls = array.join(' ');
50996             } else {
50997                 me.bodyCls = cls;
50998             }
50999         }
51000
51001         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
51002             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
51003         }
51004     },
51005
51006     onClick: function(e) {
51007         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
51008             this.fireEvent('click', e);
51009         }
51010     },
51011
51012     getTargetEl: function() {
51013         return this.body || this.frameBody || this.el;
51014     },
51015
51016     /**
51017      * Sets the title of the header.
51018      * @param {String} title The title to be set
51019      */
51020     setTitle: function(title) {
51021         var me = this;
51022         if (me.rendered) {
51023             if (me.titleCmp.rendered) {
51024                 if (me.titleCmp.surface) {
51025                     me.title = title || '';
51026                     var sprite = me.titleCmp.surface.items.items[0],
51027                         surface = me.titleCmp.surface;
51028
51029                     surface.remove(sprite);
51030                     me.textConfig.type = 'text';
51031                     me.textConfig.text = title;
51032                     sprite = surface.add(me.textConfig);
51033                     sprite.setAttributes({
51034                         rotate: {
51035                             degrees: 90
51036                         }
51037                     }, true);
51038                     me.titleCmp.autoSizeSurface();
51039                 } else {
51040                     me.title = title || '&#160;';
51041                     me.titleCmp.textEl.update(me.title);
51042                 }
51043             } else {
51044                 me.titleCmp.on({
51045                     render: function() {
51046                         me.setTitle(title);
51047                     },
51048                     single: true
51049                 });
51050             }
51051         } else {
51052             me.on({
51053                 render: function() {
51054                     me.layout.layout();
51055                     me.setTitle(title);
51056                 },
51057                 single: true
51058             });
51059         }
51060     },
51061
51062     /**
51063      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
51064      * icon class if one has already been set.
51065      * @param {String} cls The new CSS class name
51066      */
51067     setIconCls: function(cls) {
51068         var me = this,
51069             isEmpty = !cls || !cls.length,
51070             iconCmp = me.iconCmp,
51071             el;
51072         
51073         me.iconCls = cls;
51074         if (!me.iconCmp && !isEmpty) {
51075             me.initIconCmp();
51076             me.insert(0, me.iconCmp);
51077         } else if (iconCmp) {
51078             if (isEmpty) {
51079                 me.iconCmp.destroy();
51080             } else {
51081                 el = iconCmp.iconEl;
51082                 el.removeCls(iconCmp.iconCls);
51083                 el.addCls(cls);
51084                 iconCmp.iconCls = cls;
51085             }
51086         }
51087     },
51088
51089     /**
51090      * Add a tool to the header
51091      * @param {Object} tool
51092      */
51093     addTool: function(tool) {
51094         this.tools.push(this.add(tool));
51095     },
51096
51097     /**
51098      * @private
51099      * Set up the tools.&lt;tool type> link in the owning Panel.
51100      * Bind the tool to its owning Panel.
51101      * @param component
51102      * @param index
51103      */
51104     onAdd: function(component, index) {
51105         this.callParent([arguments]);
51106         if (component instanceof Ext.panel.Tool) {
51107             component.bindTo(this.ownerCt);
51108             this.tools[component.type] = component;
51109         }
51110     }
51111 });
51112
51113 /**
51114  * @class Ext.fx.target.Element
51115  * @extends Ext.fx.target.Target
51116  * 
51117  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
51118  * created directly, the {@link Ext.Element} will be passed to the animation and
51119  * and the appropriate target will be created.
51120  */
51121 Ext.define('Ext.fx.target.Element', {
51122
51123     /* Begin Definitions */
51124     
51125     extend: 'Ext.fx.target.Target',
51126     
51127     /* End Definitions */
51128
51129     type: 'element',
51130
51131     getElVal: function(el, attr, val) {
51132         if (val == undefined) {
51133             if (attr === 'x') {
51134                 val = el.getX();
51135             }
51136             else if (attr === 'y') {
51137                 val = el.getY();
51138             }
51139             else if (attr === 'scrollTop') {
51140                 val = el.getScroll().top;
51141             }
51142             else if (attr === 'scrollLeft') {
51143                 val = el.getScroll().left;
51144             }
51145             else if (attr === 'height') {
51146                 val = el.getHeight();
51147             }
51148             else if (attr === 'width') {
51149                 val = el.getWidth();
51150             }
51151             else {
51152                 val = el.getStyle(attr);
51153             }
51154         }
51155         return val;
51156     },
51157
51158     getAttr: function(attr, val) {
51159         var el = this.target;
51160         return [[ el, this.getElVal(el, attr, val)]];
51161     },
51162
51163     setAttr: function(targetData) {
51164         var target = this.target,
51165             ln = targetData.length,
51166             attrs, attr, o, i, j, ln2, element, value;
51167         for (i = 0; i < ln; i++) {
51168             attrs = targetData[i].attrs;
51169             for (attr in attrs) {
51170                 if (attrs.hasOwnProperty(attr)) {
51171                     ln2 = attrs[attr].length;
51172                     for (j = 0; j < ln2; j++) {
51173                         o = attrs[attr][j];
51174                         element = o[0];
51175                         value = o[1];
51176                         if (attr === 'x') {
51177                             element.setX(value);
51178                         }
51179                         else if (attr === 'y') {
51180                             element.setY(value);
51181                         }
51182                         else if (attr === 'scrollTop') {
51183                             element.scrollTo('top', value);
51184                         }
51185                         else if (attr === 'scrollLeft') {
51186                             element.scrollTo('left',value);
51187                         }
51188                         else {
51189                             element.setStyle(attr, value);
51190                         }
51191                     }
51192                 }
51193             }
51194         }
51195     }
51196 });
51197
51198 /**
51199  * @class Ext.fx.target.CompositeElement
51200  * @extends Ext.fx.target.Element
51201  * 
51202  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
51203  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
51204  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
51205  * and the appropriate target will be created.
51206  */
51207 Ext.define('Ext.fx.target.CompositeElement', {
51208
51209     /* Begin Definitions */
51210
51211     extend: 'Ext.fx.target.Element',
51212
51213     /* End Definitions */
51214
51215     isComposite: true,
51216     
51217     constructor: function(target) {
51218         target.id = target.id || Ext.id(null, 'ext-composite-');
51219         this.callParent([target]);
51220     },
51221
51222     getAttr: function(attr, val) {
51223         var out = [],
51224             target = this.target;
51225         target.each(function(el) {
51226             out.push([el, this.getElVal(el, attr, val)]);
51227         }, this);
51228         return out;
51229     }
51230 });
51231
51232 /**
51233  * @class Ext.fx.Manager
51234  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
51235  * @private
51236  * @singleton
51237  */
51238
51239 Ext.define('Ext.fx.Manager', {
51240
51241     /* Begin Definitions */
51242
51243     singleton: true,
51244
51245     requires: ['Ext.util.MixedCollection',
51246                'Ext.fx.target.Element',
51247                'Ext.fx.target.CompositeElement',
51248                'Ext.fx.target.Sprite',
51249                'Ext.fx.target.CompositeSprite',
51250                'Ext.fx.target.Component'],
51251
51252     mixins: {
51253         queue: 'Ext.fx.Queue'
51254     },
51255
51256     /* End Definitions */
51257
51258     constructor: function() {
51259         this.items = Ext.create('Ext.util.MixedCollection');
51260         this.mixins.queue.constructor.call(this);
51261
51262         // this.requestAnimFrame = (function() {
51263         //     var raf = window.requestAnimationFrame ||
51264         //               window.webkitRequestAnimationFrame ||
51265         //               window.mozRequestAnimationFrame ||
51266         //               window.oRequestAnimationFrame ||
51267         //               window.msRequestAnimationFrame;
51268         //     if (raf) {
51269         //         return function(callback, element) {
51270         //             raf(callback);
51271         //         };
51272         //     }
51273         //     else {
51274         //         return function(callback, element) {
51275         //             window.setTimeout(callback, Ext.fx.Manager.interval);
51276         //         };
51277         //     }
51278         // })();
51279     },
51280
51281     /**
51282      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
51283      */
51284     interval: 16,
51285
51286     /**
51287      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
51288      */
51289     forceJS: true,
51290
51291     // @private Target factory
51292     createTarget: function(target) {
51293         var me = this,
51294             useCSS3 = !me.forceJS && Ext.supports.Transitions,
51295             targetObj;
51296
51297         me.useCSS3 = useCSS3;
51298
51299         // dom id
51300         if (Ext.isString(target)) {
51301             target = Ext.get(target);
51302         }
51303         // dom element
51304         if (target && target.tagName) {
51305             target = Ext.get(target);
51306             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51307             me.targets.add(targetObj);
51308             return targetObj;
51309         }
51310         if (Ext.isObject(target)) {
51311             // Element
51312             if (target.dom) {
51313                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51314             }
51315             // Element Composite
51316             else if (target.isComposite) {
51317                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
51318             }
51319             // Draw Sprite
51320             else if (target.isSprite) {
51321                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
51322             }
51323             // Draw Sprite Composite
51324             else if (target.isCompositeSprite) {
51325                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
51326             }
51327             // Component
51328             else if (target.isComponent) {
51329                 targetObj = Ext.create('Ext.fx.target.Component', target);
51330             }
51331             else if (target.isAnimTarget) {
51332                 return target;
51333             }
51334             else {
51335                 return null;
51336             }
51337             me.targets.add(targetObj);
51338             return targetObj;
51339         }
51340         else {
51341             return null;
51342         }
51343     },
51344
51345     /**
51346      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
51347      * @param {Ext.fx.Anim} anim
51348      */
51349     addAnim: function(anim) {
51350         var items = this.items,
51351             task = this.task;
51352         // var me = this,
51353         //     items = me.items,
51354         //     cb = function() {
51355         //         if (items.length) {
51356         //             me.task = true;
51357         //             me.runner();
51358         //             me.requestAnimFrame(cb);
51359         //         }
51360         //         else {
51361         //             me.task = false;
51362         //         }
51363         //     };
51364
51365         items.add(anim);
51366
51367         // Start the timer if not already running
51368         if (!task && items.length) {
51369             task = this.task = {
51370                 run: this.runner,
51371                 interval: this.interval,
51372                 scope: this
51373             };
51374             Ext.TaskManager.start(task);
51375         }
51376
51377         // //Start the timer if not already running
51378         // if (!me.task && items.length) {
51379         //     me.requestAnimFrame(cb);
51380         // }
51381     },
51382
51383     /**
51384      * Remove an Anim from the manager. This is done automatically when an Anim ends.
51385      * @param {Ext.fx.Anim} anim
51386      */
51387     removeAnim: function(anim) {
51388         // this.items.remove(anim);
51389         var items = this.items,
51390             task = this.task;
51391         items.remove(anim);
51392         // Stop the timer if there are no more managed Anims
51393         if (task && !items.length) {
51394             Ext.TaskManager.stop(task);
51395             delete this.task;
51396         }
51397     },
51398
51399     /**
51400      * @private
51401      * Filter function to determine which animations need to be started
51402      */
51403     startingFilter: function(o) {
51404         return o.paused === false && o.running === false && o.iterations > 0;
51405     },
51406
51407     /**
51408      * @private
51409      * Filter function to determine which animations are still running
51410      */
51411     runningFilter: function(o) {
51412         return o.paused === false && o.running === true && o.isAnimator !== true;
51413     },
51414
51415     /**
51416      * @private
51417      * Runner function being called each frame
51418      */
51419     runner: function() {
51420         var me = this,
51421             items = me.items;
51422
51423         me.targetData = {};
51424         me.targetArr = {};
51425
51426         // Single timestamp for all animations this interval
51427         me.timestamp = new Date();
51428
51429         // Start any items not current running
51430         items.filterBy(me.startingFilter).each(me.startAnim, me);
51431
51432         // Build the new attributes to be applied for all targets in this frame
51433         items.filterBy(me.runningFilter).each(me.runAnim, me);
51434
51435         // Apply all the pending changes to their targets
51436         me.applyPendingAttrs();
51437     },
51438
51439     /**
51440      * @private
51441      * Start the individual animation (initialization)
51442      */
51443     startAnim: function(anim) {
51444         anim.start(this.timestamp);
51445     },
51446
51447     /**
51448      * @private
51449      * Run the individual animation for this frame
51450      */
51451     runAnim: function(anim) {
51452         if (!anim) {
51453             return;
51454         }
51455         var me = this,
51456             targetId = anim.target.getId(),
51457             useCSS3 = me.useCSS3 && anim.target.type == 'element',
51458             elapsedTime = me.timestamp - anim.startTime,
51459             target, o;
51460
51461         this.collectTargetData(anim, elapsedTime, useCSS3);
51462
51463         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
51464         // to get a good initial state, then add the transition properties and set the final attributes.
51465         if (useCSS3) {
51466             // Flush the collected attributes, without transition
51467             anim.target.setAttr(me.targetData[targetId], true);
51468
51469             // Add the end frame data
51470             me.targetData[targetId] = [];
51471             me.collectTargetData(anim, anim.duration, useCSS3);
51472
51473             // Pause the animation so runAnim doesn't keep getting called
51474             anim.paused = true;
51475
51476             target = anim.target.target;
51477             // We only want to attach an event on the last element in a composite
51478             if (anim.target.isComposite) {
51479                 target = anim.target.target.last();
51480             }
51481
51482             // Listen for the transitionend event
51483             o = {};
51484             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
51485             o.scope = anim;
51486             o.single = true;
51487             target.on(o);
51488         }
51489         // For JS animation, trigger the lastFrame handler if this is the final frame
51490         else if (elapsedTime >= anim.duration) {
51491             me.applyPendingAttrs(true);
51492             delete me.targetData[targetId];
51493             delete me.targetArr[targetId];
51494             anim.lastFrame();
51495         }
51496     },
51497
51498     /**
51499      * Collect target attributes for the given Anim object at the given timestamp
51500      * @param {Ext.fx.Anim} anim The Anim instance
51501      * @param {Number} timestamp Time after the anim's start time
51502      */
51503     collectTargetData: function(anim, elapsedTime, useCSS3) {
51504         var targetId = anim.target.getId(),
51505             targetData = this.targetData[targetId],
51506             data;
51507         
51508         if (!targetData) {
51509             targetData = this.targetData[targetId] = [];
51510             this.targetArr[targetId] = anim.target;
51511         }
51512
51513         data = {
51514             duration: anim.duration,
51515             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
51516             attrs: {}
51517         };
51518         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
51519         targetData.push(data);
51520     },
51521
51522     /**
51523      * @private
51524      * Apply all pending attribute changes to their targets
51525      */
51526     applyPendingAttrs: function(isLastFrame) {
51527         var targetData = this.targetData,
51528             targetArr = this.targetArr,
51529             targetId;
51530         for (targetId in targetData) {
51531             if (targetData.hasOwnProperty(targetId)) {
51532                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
51533             }
51534         }
51535     }
51536 });
51537
51538 /**
51539  * @class Ext.fx.Animator
51540  *
51541  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
51542  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
51543  * at various points throughout the animation.
51544  *
51545  * ## Using Keyframes
51546  *
51547  * The {@link #keyframes} option is the most important part of specifying an animation when using this
51548  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
51549  * total animation duration. At each key frame, we can specify the target values at that time. Note that
51550  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
51551  * event that fires after each key frame is reached.
51552  *
51553  * ## Example
51554  *
51555  * In the example below, we modify the values of the element at each fifth throughout the animation.
51556  *
51557  *     @example
51558  *     Ext.create('Ext.fx.Animator', {
51559  *         target: Ext.getBody().createChild({
51560  *             style: {
51561  *                 width: '100px',
51562  *                 height: '100px',
51563  *                 'background-color': 'red'
51564  *             }
51565  *         }),
51566  *         duration: 10000, // 10 seconds
51567  *         keyframes: {
51568  *             0: {
51569  *                 opacity: 1,
51570  *                 backgroundColor: 'FF0000'
51571  *             },
51572  *             20: {
51573  *                 x: 30,
51574  *                 opacity: 0.5
51575  *             },
51576  *             40: {
51577  *                 x: 130,
51578  *                 backgroundColor: '0000FF'
51579  *             },
51580  *             60: {
51581  *                 y: 80,
51582  *                 opacity: 0.3
51583  *             },
51584  *             80: {
51585  *                 width: 200,
51586  *                 y: 200
51587  *             },
51588  *             100: {
51589  *                 opacity: 1,
51590  *                 backgroundColor: '00FF00'
51591  *             }
51592  *         }
51593  *     });
51594  */
51595 Ext.define('Ext.fx.Animator', {
51596
51597     /* Begin Definitions */
51598
51599     mixins: {
51600         observable: 'Ext.util.Observable'
51601     },
51602
51603     requires: ['Ext.fx.Manager'],
51604
51605     /* End Definitions */
51606
51607     isAnimator: true,
51608
51609     /**
51610      * @cfg {Number} duration
51611      * Time in milliseconds for the animation to last. Defaults to 250.
51612      */
51613     duration: 250,
51614
51615     /**
51616      * @cfg {Number} delay
51617      * Time to delay before starting the animation. Defaults to 0.
51618      */
51619     delay: 0,
51620
51621     /* private used to track a delayed starting time */
51622     delayStart: 0,
51623
51624     /**
51625      * @cfg {Boolean} dynamic
51626      * 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.
51627      */
51628     dynamic: false,
51629
51630     /**
51631      * @cfg {String} easing
51632      *
51633      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
51634      * speed over its duration.
51635      *
51636      *  - backIn
51637      *  - backOut
51638      *  - bounceIn
51639      *  - bounceOut
51640      *  - ease
51641      *  - easeIn
51642      *  - easeOut
51643      *  - easeInOut
51644      *  - elasticIn
51645      *  - elasticOut
51646      *  - cubic-bezier(x1, y1, x2, y2)
51647      *
51648      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51649      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51650      * be in the range [0, 1] or the definition is invalid.
51651      *
51652      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51653      */
51654     easing: 'ease',
51655
51656     /**
51657      * Flag to determine if the animation has started
51658      * @property running
51659      * @type Boolean
51660      */
51661     running: false,
51662
51663     /**
51664      * Flag to determine if the animation is paused. Only set this to true if you need to
51665      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
51666      * @property paused
51667      * @type Boolean
51668      */
51669     paused: false,
51670
51671     /**
51672      * @private
51673      */
51674     damper: 1,
51675
51676     /**
51677      * @cfg {Number} iterations
51678      * Number of times to execute the animation. Defaults to 1.
51679      */
51680     iterations: 1,
51681
51682     /**
51683      * Current iteration the animation is running.
51684      * @property currentIteration
51685      * @type Number
51686      */
51687     currentIteration: 0,
51688
51689     /**
51690      * Current keyframe step of the animation.
51691      * @property keyframeStep
51692      * @type Number
51693      */
51694     keyframeStep: 0,
51695
51696     /**
51697      * @private
51698      */
51699     animKeyFramesRE: /^(from|to|\d+%?)$/,
51700
51701     /**
51702      * @cfg {Ext.fx.target.Target} target
51703      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
51704      * method to apply the same animation to many targets.
51705      */
51706
51707      /**
51708       * @cfg {Object} keyframes
51709       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
51710       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
51711       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
51712       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
51713       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
51714  <pre><code>
51715 keyframes : {
51716     '0%': {
51717         left: 100
51718     },
51719     '40%': {
51720         left: 150
51721     },
51722     '60%': {
51723         left: 75
51724     },
51725     '100%': {
51726         left: 100
51727     }
51728 }
51729  </code></pre>
51730       */
51731     constructor: function(config) {
51732         var me = this;
51733         config = Ext.apply(me, config || {});
51734         me.config = config;
51735         me.id = Ext.id(null, 'ext-animator-');
51736         me.addEvents(
51737             /**
51738              * @event beforeanimate
51739              * Fires before the animation starts. A handler can return false to cancel the animation.
51740              * @param {Ext.fx.Animator} this
51741              */
51742             'beforeanimate',
51743             /**
51744               * @event keyframe
51745               * Fires at each keyframe.
51746               * @param {Ext.fx.Animator} this
51747               * @param {Number} keyframe step number
51748               */
51749             'keyframe',
51750             /**
51751              * @event afteranimate
51752              * Fires when the animation is complete.
51753              * @param {Ext.fx.Animator} this
51754              * @param {Date} startTime
51755              */
51756             'afteranimate'
51757         );
51758         me.mixins.observable.constructor.call(me, config);
51759         me.timeline = [];
51760         me.createTimeline(me.keyframes);
51761         if (me.target) {
51762             me.applyAnimator(me.target);
51763             Ext.fx.Manager.addAnim(me);
51764         }
51765     },
51766
51767     /**
51768      * @private
51769      */
51770     sorter: function (a, b) {
51771         return a.pct - b.pct;
51772     },
51773
51774     /**
51775      * @private
51776      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
51777      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
51778      */
51779     createTimeline: function(keyframes) {
51780         var me = this,
51781             attrs = [],
51782             to = me.to || {},
51783             duration = me.duration,
51784             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
51785
51786         for (pct in keyframes) {
51787             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
51788                 attr = {attrs: Ext.apply(keyframes[pct], to)};
51789                 // CSS3 spec allow for from/to to be specified.
51790                 if (pct == "from") {
51791                     pct = 0;
51792                 }
51793                 else if (pct == "to") {
51794                     pct = 100;
51795                 }
51796                 // convert % values into integers
51797                 attr.pct = parseInt(pct, 10);
51798                 attrs.push(attr);
51799             }
51800         }
51801         // Sort by pct property
51802         Ext.Array.sort(attrs, me.sorter);
51803         // Only an end
51804         //if (attrs[0].pct) {
51805         //    attrs.unshift({pct: 0, attrs: element.attrs});
51806         //}
51807
51808         ln = attrs.length;
51809         for (i = 0; i < ln; i++) {
51810             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
51811             ms = duration * (attrs[i].pct / 100);
51812             me.timeline.push({
51813                 duration: ms - prevMs,
51814                 attrs: attrs[i].attrs
51815             });
51816         }
51817     },
51818
51819     /**
51820      * Applies animation to the Ext.fx.target
51821      * @private
51822      * @param target
51823      * @type String/Object
51824      */
51825     applyAnimator: function(target) {
51826         var me = this,
51827             anims = [],
51828             timeline = me.timeline,
51829             reverse = me.reverse,
51830             ln = timeline.length,
51831             anim, easing, damper, initial, attrs, lastAttrs, i;
51832
51833         if (me.fireEvent('beforeanimate', me) !== false) {
51834             for (i = 0; i < ln; i++) {
51835                 anim = timeline[i];
51836                 attrs = anim.attrs;
51837                 easing = attrs.easing || me.easing;
51838                 damper = attrs.damper || me.damper;
51839                 delete attrs.easing;
51840                 delete attrs.damper;
51841                 anim = Ext.create('Ext.fx.Anim', {
51842                     target: target,
51843                     easing: easing,
51844                     damper: damper,
51845                     duration: anim.duration,
51846                     paused: true,
51847                     to: attrs
51848                 });
51849                 anims.push(anim);
51850             }
51851             me.animations = anims;
51852             me.target = anim.target;
51853             for (i = 0; i < ln - 1; i++) {
51854                 anim = anims[i];
51855                 anim.nextAnim = anims[i + 1];
51856                 anim.on('afteranimate', function() {
51857                     this.nextAnim.paused = false;
51858                 });
51859                 anim.on('afteranimate', function() {
51860                     this.fireEvent('keyframe', this, ++this.keyframeStep);
51861                 }, me);
51862             }
51863             anims[ln - 1].on('afteranimate', function() {
51864                 this.lastFrame();
51865             }, me);
51866         }
51867     },
51868
51869     /**
51870      * @private
51871      * Fires beforeanimate and sets the running flag.
51872      */
51873     start: function(startTime) {
51874         var me = this,
51875             delay = me.delay,
51876             delayStart = me.delayStart,
51877             delayDelta;
51878         if (delay) {
51879             if (!delayStart) {
51880                 me.delayStart = startTime;
51881                 return;
51882             }
51883             else {
51884                 delayDelta = startTime - delayStart;
51885                 if (delayDelta < delay) {
51886                     return;
51887                 }
51888                 else {
51889                     // Compensate for frame delay;
51890                     startTime = new Date(delayStart.getTime() + delay);
51891                 }
51892             }
51893         }
51894         if (me.fireEvent('beforeanimate', me) !== false) {
51895             me.startTime = startTime;
51896             me.running = true;
51897             me.animations[me.keyframeStep].paused = false;
51898         }
51899     },
51900
51901     /**
51902      * @private
51903      * Perform lastFrame cleanup and handle iterations
51904      * @returns a hash of the new attributes.
51905      */
51906     lastFrame: function() {
51907         var me = this,
51908             iter = me.iterations,
51909             iterCount = me.currentIteration;
51910
51911         iterCount++;
51912         if (iterCount < iter) {
51913             me.startTime = new Date();
51914             me.currentIteration = iterCount;
51915             me.keyframeStep = 0;
51916             me.applyAnimator(me.target);
51917             me.animations[me.keyframeStep].paused = false;
51918         }
51919         else {
51920             me.currentIteration = 0;
51921             me.end();
51922         }
51923     },
51924
51925     /**
51926      * Fire afteranimate event and end the animation. Usually called automatically when the
51927      * animation reaches its final frame, but can also be called manually to pre-emptively
51928      * stop and destroy the running animation.
51929      */
51930     end: function() {
51931         var me = this;
51932         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
51933     }
51934 });
51935 /**
51936  * @class Ext.fx.Easing
51937  *
51938  * This class contains a series of function definitions used to modify values during an animation.
51939  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
51940  * speed over its duration. The following options are available: 
51941  *
51942  * - linear The default easing type
51943  * - backIn
51944  * - backOut
51945  * - bounceIn
51946  * - bounceOut
51947  * - ease
51948  * - easeIn
51949  * - easeOut
51950  * - easeInOut
51951  * - elasticIn
51952  * - elasticOut
51953  * - cubic-bezier(x1, y1, x2, y2)
51954  *
51955  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51956  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51957  * be in the range [0, 1] or the definition is invalid.
51958  *
51959  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51960  *
51961  * @singleton
51962  */
51963 Ext.ns('Ext.fx');
51964
51965 Ext.require('Ext.fx.CubicBezier', function() {
51966     var math = Math,
51967         pi = math.PI,
51968         pow = math.pow,
51969         sin = math.sin,
51970         sqrt = math.sqrt,
51971         abs = math.abs,
51972         backInSeed = 1.70158;
51973     Ext.fx.Easing = {
51974         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
51975         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
51976         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51977         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51978         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
51979         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51980         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51981         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
51982     };
51983
51984     Ext.apply(Ext.fx.Easing, {
51985         linear: function(n) {
51986             return n;
51987         },
51988         ease: function(n) {
51989             var q = 0.07813 - n / 2,
51990                 alpha = -0.25,
51991                 Q = sqrt(0.0066 + q * q),
51992                 x = Q - q,
51993                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
51994                 y = -Q - q,
51995                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
51996                 t = X + Y + 0.25;
51997             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
51998         },
51999         easeIn: function (n) {
52000             return pow(n, 1.7);
52001         },
52002         easeOut: function (n) {
52003             return pow(n, 0.48);
52004         },
52005         easeInOut: function(n) {
52006             var q = 0.48 - n / 1.04,
52007                 Q = sqrt(0.1734 + q * q),
52008                 x = Q - q,
52009                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52010                 y = -Q - q,
52011                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52012                 t = X + Y + 0.5;
52013             return (1 - t) * 3 * t * t + t * t * t;
52014         },
52015         backIn: function (n) {
52016             return n * n * ((backInSeed + 1) * n - backInSeed);
52017         },
52018         backOut: function (n) {
52019             n = n - 1;
52020             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
52021         },
52022         elasticIn: function (n) {
52023             if (n === 0 || n === 1) {
52024                 return n;
52025             }
52026             var p = 0.3,
52027                 s = p / 4;
52028             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
52029         },
52030         elasticOut: function (n) {
52031             return 1 - Ext.fx.Easing.elasticIn(1 - n);
52032         },
52033         bounceIn: function (n) {
52034             return 1 - Ext.fx.Easing.bounceOut(1 - n);
52035         },
52036         bounceOut: function (n) {
52037             var s = 7.5625,
52038                 p = 2.75,
52039                 l;
52040             if (n < (1 / p)) {
52041                 l = s * n * n;
52042             } else {
52043                 if (n < (2 / p)) {
52044                     n -= (1.5 / p);
52045                     l = s * n * n + 0.75;
52046                 } else {
52047                     if (n < (2.5 / p)) {
52048                         n -= (2.25 / p);
52049                         l = s * n * n + 0.9375;
52050                     } else {
52051                         n -= (2.625 / p);
52052                         l = s * n * n + 0.984375;
52053                     }
52054                 }
52055             }
52056             return l;
52057         }
52058     });
52059     Ext.apply(Ext.fx.Easing, {
52060         'back-in': Ext.fx.Easing.backIn,
52061         'back-out': Ext.fx.Easing.backOut,
52062         'ease-in': Ext.fx.Easing.easeIn,
52063         'ease-out': Ext.fx.Easing.easeOut,
52064         'elastic-in': Ext.fx.Easing.elasticIn,
52065         'elastic-out': Ext.fx.Easing.elasticIn,
52066         'bounce-in': Ext.fx.Easing.bounceIn,
52067         'bounce-out': Ext.fx.Easing.bounceOut,
52068         'ease-in-out': Ext.fx.Easing.easeInOut
52069     });
52070 });
52071 /**
52072  * @class Ext.draw.Draw
52073  * Base Drawing class.  Provides base drawing functions.
52074  * @private
52075  */
52076 Ext.define('Ext.draw.Draw', {
52077     /* Begin Definitions */
52078
52079     singleton: true,
52080
52081     requires: ['Ext.draw.Color'],
52082
52083     /* End Definitions */
52084
52085     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
52086     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
52087     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
52088     stopsRE: /^(\d+%?)$/,
52089     radian: Math.PI / 180,
52090
52091     availableAnimAttrs: {
52092         along: "along",
52093         blur: null,
52094         "clip-rect": "csv",
52095         cx: null,
52096         cy: null,
52097         fill: "color",
52098         "fill-opacity": null,
52099         "font-size": null,
52100         height: null,
52101         opacity: null,
52102         path: "path",
52103         r: null,
52104         rotation: "csv",
52105         rx: null,
52106         ry: null,
52107         scale: "csv",
52108         stroke: "color",
52109         "stroke-opacity": null,
52110         "stroke-width": null,
52111         translation: "csv",
52112         width: null,
52113         x: null,
52114         y: null
52115     },
52116
52117     is: function(o, type) {
52118         type = String(type).toLowerCase();
52119         return (type == "object" && o === Object(o)) ||
52120             (type == "undefined" && typeof o == type) ||
52121             (type == "null" && o === null) ||
52122             (type == "array" && Array.isArray && Array.isArray(o)) ||
52123             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
52124     },
52125
52126     ellipsePath: function(sprite) {
52127         var attr = sprite.attr;
52128         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);
52129     },
52130
52131     rectPath: function(sprite) {
52132         var attr = sprite.attr;
52133         if (attr.radius) {
52134             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);
52135         }
52136         else {
52137             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
52138         }
52139     },
52140
52141     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
52142     path2string: function () {
52143         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52144     },
52145
52146     // Convert the passed arrayPath to a proper SVG path string (d attribute)
52147     pathToString: function(arrayPath) {
52148         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52149     },
52150
52151     parsePathString: function (pathString) {
52152         if (!pathString) {
52153             return null;
52154         }
52155         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
52156             data = [],
52157             me = this;
52158         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
52159             data = me.pathClone(pathString);
52160         }
52161         if (!data.length) {
52162             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
52163                 var params = [],
52164                     name = b.toLowerCase();
52165                 c.replace(me.pathValuesRE, function (a, b) {
52166                     b && params.push(+b);
52167                 });
52168                 if (name == "m" && params.length > 2) {
52169                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
52170                     name = "l";
52171                     b = (b == "m") ? "l" : "L";
52172                 }
52173                 while (params.length >= paramCounts[name]) {
52174                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
52175                     if (!paramCounts[name]) {
52176                         break;
52177                     }
52178                 }
52179             });
52180         }
52181         data.toString = me.path2string;
52182         return data;
52183     },
52184
52185     mapPath: function (path, matrix) {
52186         if (!matrix) {
52187             return path;
52188         }
52189         var x, y, i, ii, j, jj, pathi;
52190         path = this.path2curve(path);
52191         for (i = 0, ii = path.length; i < ii; i++) {
52192             pathi = path[i];
52193             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
52194                 x = matrix.x(pathi[j], pathi[j + 1]);
52195                 y = matrix.y(pathi[j], pathi[j + 1]);
52196                 pathi[j] = x;
52197                 pathi[j + 1] = y;
52198             }
52199         }
52200         return path;
52201     },
52202
52203     pathClone: function(pathArray) {
52204         var res = [],
52205             j, jj, i, ii;
52206         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52207             pathArray = this.parsePathString(pathArray);
52208         }
52209         for (i = 0, ii = pathArray.length; i < ii; i++) {
52210             res[i] = [];
52211             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
52212                 res[i][j] = pathArray[i][j];
52213             }
52214         }
52215         res.toString = this.path2string;
52216         return res;
52217     },
52218
52219     pathToAbsolute: function (pathArray) {
52220         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52221             pathArray = this.parsePathString(pathArray);
52222         }
52223         var res = [],
52224             x = 0,
52225             y = 0,
52226             mx = 0,
52227             my = 0,
52228             i = 0,
52229             ln = pathArray.length,
52230             r, pathSegment, j, ln2;
52231         // MoveTo initial x/y position
52232         if (ln && pathArray[0][0] == "M") {
52233             x = +pathArray[0][1];
52234             y = +pathArray[0][2];
52235             mx = x;
52236             my = y;
52237             i++;
52238             res[0] = ["M", x, y];
52239         }
52240         for (; i < ln; i++) {
52241             r = res[i] = [];
52242             pathSegment = pathArray[i];
52243             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
52244                 r[0] = pathSegment[0].toUpperCase();
52245                 switch (r[0]) {
52246                     // Elliptical Arc
52247                     case "A":
52248                         r[1] = pathSegment[1];
52249                         r[2] = pathSegment[2];
52250                         r[3] = pathSegment[3];
52251                         r[4] = pathSegment[4];
52252                         r[5] = pathSegment[5];
52253                         r[6] = +(pathSegment[6] + x);
52254                         r[7] = +(pathSegment[7] + y);
52255                         break;
52256                     // Vertical LineTo
52257                     case "V":
52258                         r[1] = +pathSegment[1] + y;
52259                         break;
52260                     // Horizontal LineTo
52261                     case "H":
52262                         r[1] = +pathSegment[1] + x;
52263                         break;
52264                     case "M":
52265                     // MoveTo
52266                         mx = +pathSegment[1] + x;
52267                         my = +pathSegment[2] + y;
52268                     default:
52269                         j = 1;
52270                         ln2 = pathSegment.length;
52271                         for (; j < ln2; j++) {
52272                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
52273                         }
52274                 }
52275             }
52276             else {
52277                 j = 0;
52278                 ln2 = pathSegment.length;
52279                 for (; j < ln2; j++) {
52280                     res[i][j] = pathSegment[j];
52281                 }
52282             }
52283             switch (r[0]) {
52284                 // ClosePath
52285                 case "Z":
52286                     x = mx;
52287                     y = my;
52288                     break;
52289                 // Horizontal LineTo
52290                 case "H":
52291                     x = r[1];
52292                     break;
52293                 // Vertical LineTo
52294                 case "V":
52295                     y = r[1];
52296                     break;
52297                 // MoveTo
52298                 case "M":
52299                     pathSegment = res[i];
52300                     ln2 = pathSegment.length;
52301                     mx = pathSegment[ln2 - 2];
52302                     my = pathSegment[ln2 - 1];
52303                 default:
52304                     pathSegment = res[i];
52305                     ln2 = pathSegment.length;
52306                     x = pathSegment[ln2 - 2];
52307                     y = pathSegment[ln2 - 1];
52308             }
52309         }
52310         res.toString = this.path2string;
52311         return res;
52312     },
52313
52314     // TO BE DEPRECATED
52315     pathToRelative: function (pathArray) {
52316         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
52317             pathArray = this.parsePathString(pathArray);
52318         }
52319         var res = [],
52320             x = 0,
52321             y = 0,
52322             mx = 0,
52323             my = 0,
52324             start = 0;
52325         if (pathArray[0][0] == "M") {
52326             x = pathArray[0][1];
52327             y = pathArray[0][2];
52328             mx = x;
52329             my = y;
52330             start++;
52331             res.push(["M", x, y]);
52332         }
52333         for (var i = start, ii = pathArray.length; i < ii; i++) {
52334             var r = res[i] = [],
52335                 pa = pathArray[i];
52336             if (pa[0] != pa[0].toLowerCase()) {
52337                 r[0] = pa[0].toLowerCase();
52338                 switch (r[0]) {
52339                     case "a":
52340                         r[1] = pa[1];
52341                         r[2] = pa[2];
52342                         r[3] = pa[3];
52343                         r[4] = pa[4];
52344                         r[5] = pa[5];
52345                         r[6] = +(pa[6] - x).toFixed(3);
52346                         r[7] = +(pa[7] - y).toFixed(3);
52347                         break;
52348                     case "v":
52349                         r[1] = +(pa[1] - y).toFixed(3);
52350                         break;
52351                     case "m":
52352                         mx = pa[1];
52353                         my = pa[2];
52354                     default:
52355                         for (var j = 1, jj = pa.length; j < jj; j++) {
52356                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
52357                         }
52358                 }
52359             } else {
52360                 r = res[i] = [];
52361                 if (pa[0] == "m") {
52362                     mx = pa[1] + x;
52363                     my = pa[2] + y;
52364                 }
52365                 for (var k = 0, kk = pa.length; k < kk; k++) {
52366                     res[i][k] = pa[k];
52367                 }
52368             }
52369             var len = res[i].length;
52370             switch (res[i][0]) {
52371                 case "z":
52372                     x = mx;
52373                     y = my;
52374                     break;
52375                 case "h":
52376                     x += +res[i][len - 1];
52377                     break;
52378                 case "v":
52379                     y += +res[i][len - 1];
52380                     break;
52381                 default:
52382                     x += +res[i][len - 2];
52383                     y += +res[i][len - 1];
52384             }
52385         }
52386         res.toString = this.path2string;
52387         return res;
52388     },
52389
52390     // Returns a path converted to a set of curveto commands
52391     path2curve: function (path) {
52392         var me = this,
52393             points = me.pathToAbsolute(path),
52394             ln = points.length,
52395             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52396             i, seg, segLn, point;
52397             
52398         for (i = 0; i < ln; i++) {
52399             points[i] = me.command2curve(points[i], attrs);
52400             if (points[i].length > 7) {
52401                     points[i].shift();
52402                     point = points[i];
52403                     while (point.length) {
52404                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
52405                     }
52406                     Ext.Array.erase(points, i, 1);
52407                     ln = points.length;
52408                 }
52409             seg = points[i];
52410             segLn = seg.length;
52411             attrs.x = seg[segLn - 2];
52412             attrs.y = seg[segLn - 1];
52413             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
52414             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
52415         }
52416         return points;
52417     },
52418     
52419     interpolatePaths: function (path, path2) {
52420         var me = this,
52421             p = me.pathToAbsolute(path),
52422             p2 = me.pathToAbsolute(path2),
52423             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52424             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52425             fixArc = function (pp, i) {
52426                 if (pp[i].length > 7) {
52427                     pp[i].shift();
52428                     var pi = pp[i];
52429                     while (pi.length) {
52430                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
52431                     }
52432                     Ext.Array.erase(pp, i, 1);
52433                     ii = Math.max(p.length, p2.length || 0);
52434                 }
52435             },
52436             fixM = function (path1, path2, a1, a2, i) {
52437                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
52438                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
52439                     a1.bx = 0;
52440                     a1.by = 0;
52441                     a1.x = path1[i][1];
52442                     a1.y = path1[i][2];
52443                     ii = Math.max(p.length, p2.length || 0);
52444                 }
52445             };
52446         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
52447             p[i] = me.command2curve(p[i], attrs);
52448             fixArc(p, i);
52449             (p2[i] = me.command2curve(p2[i], attrs2));
52450             fixArc(p2, i);
52451             fixM(p, p2, attrs, attrs2, i);
52452             fixM(p2, p, attrs2, attrs, i);
52453             var seg = p[i],
52454                 seg2 = p2[i],
52455                 seglen = seg.length,
52456                 seg2len = seg2.length;
52457             attrs.x = seg[seglen - 2];
52458             attrs.y = seg[seglen - 1];
52459             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
52460             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
52461             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
52462             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
52463             attrs2.x = seg2[seg2len - 2];
52464             attrs2.y = seg2[seg2len - 1];
52465         }
52466         return [p, p2];
52467     },
52468     
52469     //Returns any path command as a curveto command based on the attrs passed
52470     command2curve: function (pathCommand, d) {
52471         var me = this;
52472         if (!pathCommand) {
52473             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
52474         }
52475         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
52476             d.qx = d.qy = null;
52477         }
52478         switch (pathCommand[0]) {
52479             case "M":
52480                 d.X = pathCommand[1];
52481                 d.Y = pathCommand[2];
52482                 break;
52483             case "A":
52484                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
52485                 break;
52486             case "S":
52487                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
52488                 break;
52489             case "T":
52490                 d.qx = d.x + (d.x - (d.qx || d.x));
52491                 d.qy = d.y + (d.y - (d.qy || d.y));
52492                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
52493                 break;
52494             case "Q":
52495                 d.qx = pathCommand[1];
52496                 d.qy = pathCommand[2];
52497                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
52498                 break;
52499             case "L":
52500                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
52501                 break;
52502             case "H":
52503                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
52504                 break;
52505             case "V":
52506                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
52507                 break;
52508             case "Z":
52509                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
52510                 break;
52511         }
52512         return pathCommand;
52513     },
52514
52515     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
52516         var _13 = 1 / 3,
52517             _23 = 2 / 3;
52518         return [
52519                 _13 * x1 + _23 * ax,
52520                 _13 * y1 + _23 * ay,
52521                 _13 * x2 + _23 * ax,
52522                 _13 * y2 + _23 * ay,
52523                 x2,
52524                 y2
52525             ];
52526     },
52527     
52528     rotate: function (x, y, rad) {
52529         var cos = Math.cos(rad),
52530             sin = Math.sin(rad),
52531             X = x * cos - y * sin,
52532             Y = x * sin + y * cos;
52533         return {x: X, y: Y};
52534     },
52535
52536     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
52537         // for more information of where this Math came from visit:
52538         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
52539         var me = this,
52540             PI = Math.PI,
52541             radian = me.radian,
52542             _120 = PI * 120 / 180,
52543             rad = radian * (+angle || 0),
52544             res = [],
52545             math = Math,
52546             mcos = math.cos,
52547             msin = math.sin,
52548             msqrt = math.sqrt,
52549             mabs = math.abs,
52550             masin = math.asin,
52551             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
52552             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
52553         if (!recursive) {
52554             xy = me.rotate(x1, y1, -rad);
52555             x1 = xy.x;
52556             y1 = xy.y;
52557             xy = me.rotate(x2, y2, -rad);
52558             x2 = xy.x;
52559             y2 = xy.y;
52560             cos = mcos(radian * angle);
52561             sin = msin(radian * angle);
52562             x = (x1 - x2) / 2;
52563             y = (y1 - y2) / 2;
52564             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
52565             if (h > 1) {
52566                 h = msqrt(h);
52567                 rx = h * rx;
52568                 ry = h * ry;
52569             }
52570             rx2 = rx * rx;
52571             ry2 = ry * ry;
52572             k = (large_arc_flag == sweep_flag ? -1 : 1) *
52573                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
52574             cx = k * rx * y / ry + (x1 + x2) / 2;
52575             cy = k * -ry * x / rx + (y1 + y2) / 2;
52576             f1 = masin(((y1 - cy) / ry).toFixed(7));
52577             f2 = masin(((y2 - cy) / ry).toFixed(7));
52578
52579             f1 = x1 < cx ? PI - f1 : f1;
52580             f2 = x2 < cx ? PI - f2 : f2;
52581             if (f1 < 0) {
52582                 f1 = PI * 2 + f1;
52583             }
52584             if (f2 < 0) {
52585                 f2 = PI * 2 + f2;
52586             }
52587             if (sweep_flag && f1 > f2) {
52588                 f1 = f1 - PI * 2;
52589             }
52590             if (!sweep_flag && f2 > f1) {
52591                 f2 = f2 - PI * 2;
52592             }
52593         }
52594         else {
52595             f1 = recursive[0];
52596             f2 = recursive[1];
52597             cx = recursive[2];
52598             cy = recursive[3];
52599         }
52600         df = f2 - f1;
52601         if (mabs(df) > _120) {
52602             f2old = f2;
52603             x2old = x2;
52604             y2old = y2;
52605             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
52606             x2 = cx + rx * mcos(f2);
52607             y2 = cy + ry * msin(f2);
52608             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
52609         }
52610         df = f2 - f1;
52611         c1 = mcos(f1);
52612         s1 = msin(f1);
52613         c2 = mcos(f2);
52614         s2 = msin(f2);
52615         t = math.tan(df / 4);
52616         hx = 4 / 3 * rx * t;
52617         hy = 4 / 3 * ry * t;
52618         m1 = [x1, y1];
52619         m2 = [x1 + hx * s1, y1 - hy * c1];
52620         m3 = [x2 + hx * s2, y2 - hy * c2];
52621         m4 = [x2, y2];
52622         m2[0] = 2 * m1[0] - m2[0];
52623         m2[1] = 2 * m1[1] - m2[1];
52624         if (recursive) {
52625             return [m2, m3, m4].concat(res);
52626         }
52627         else {
52628             res = [m2, m3, m4].concat(res).join().split(",");
52629             newres = [];
52630             ln = res.length;
52631             for (i = 0;  i < ln; i++) {
52632                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
52633             }
52634             return newres;
52635         }
52636     },
52637
52638     // TO BE DEPRECATED
52639     rotateAndTranslatePath: function (sprite) {
52640         var alpha = sprite.rotation.degrees,
52641             cx = sprite.rotation.x,
52642             cy = sprite.rotation.y,
52643             dx = sprite.translation.x,
52644             dy = sprite.translation.y,
52645             path,
52646             i,
52647             p,
52648             xy,
52649             j,
52650             res = [];
52651         if (!alpha && !dx && !dy) {
52652             return this.pathToAbsolute(sprite.attr.path);
52653         }
52654         dx = dx || 0;
52655         dy = dy || 0;
52656         path = this.pathToAbsolute(sprite.attr.path);
52657         for (i = path.length; i--;) {
52658             p = res[i] = path[i].slice();
52659             if (p[0] == "A") {
52660                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
52661                 p[6] = xy.x + dx;
52662                 p[7] = xy.y + dy;
52663             } else {
52664                 j = 1;
52665                 while (p[j + 1] != null) {
52666                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
52667                     p[j] = xy.x + dx;
52668                     p[j + 1] = xy.y + dy;
52669                     j += 2;
52670                 }
52671             }
52672         }
52673         return res;
52674     },
52675
52676     // TO BE DEPRECATED
52677     rotatePoint: function (x, y, alpha, cx, cy) {
52678         if (!alpha) {
52679             return {
52680                 x: x,
52681                 y: y
52682             };
52683         }
52684         cx = cx || 0;
52685         cy = cy || 0;
52686         x = x - cx;
52687         y = y - cy;
52688         alpha = alpha * this.radian;
52689         var cos = Math.cos(alpha),
52690             sin = Math.sin(alpha);
52691         return {
52692             x: x * cos - y * sin + cx,
52693             y: x * sin + y * cos + cy
52694         };
52695     },
52696
52697     pathDimensions: function (path) {
52698         if (!path || !(path + "")) {
52699             return {x: 0, y: 0, width: 0, height: 0};
52700         }
52701         path = this.path2curve(path);
52702         var x = 0, 
52703             y = 0,
52704             X = [],
52705             Y = [],
52706             i = 0,
52707             ln = path.length,
52708             p, xmin, ymin, dim;
52709         for (; i < ln; i++) {
52710             p = path[i];
52711             if (p[0] == "M") {
52712                 x = p[1];
52713                 y = p[2];
52714                 X.push(x);
52715                 Y.push(y);
52716             }
52717             else {
52718                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
52719                 X = X.concat(dim.min.x, dim.max.x);
52720                 Y = Y.concat(dim.min.y, dim.max.y);
52721                 x = p[5];
52722                 y = p[6];
52723             }
52724         }
52725         xmin = Math.min.apply(0, X);
52726         ymin = Math.min.apply(0, Y);
52727         return {
52728             x: xmin,
52729             y: ymin,
52730             path: path,
52731             width: Math.max.apply(0, X) - xmin,
52732             height: Math.max.apply(0, Y) - ymin
52733         };
52734     },
52735
52736     intersectInside: function(path, cp1, cp2) {
52737         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
52738     },
52739
52740     intersectIntersection: function(s, e, cp1, cp2) {
52741         var p = [],
52742             dcx = cp1[0] - cp2[0],
52743             dcy = cp1[1] - cp2[1],
52744             dpx = s[0] - e[0],
52745             dpy = s[1] - e[1],
52746             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
52747             n2 = s[0] * e[1] - s[1] * e[0],
52748             n3 = 1 / (dcx * dpy - dcy * dpx);
52749
52750         p[0] = (n1 * dpx - n2 * dcx) * n3;
52751         p[1] = (n1 * dpy - n2 * dcy) * n3;
52752         return p;
52753     },
52754
52755     intersect: function(subjectPolygon, clipPolygon) {
52756         var me = this,
52757             i = 0,
52758             ln = clipPolygon.length,
52759             cp1 = clipPolygon[ln - 1],
52760             outputList = subjectPolygon,
52761             cp2, s, e, point, ln2, inputList, j;
52762         for (; i < ln; ++i) {
52763             cp2 = clipPolygon[i];
52764             inputList = outputList;
52765             outputList = [];
52766             s = inputList[inputList.length - 1];
52767             j = 0;
52768             ln2 = inputList.length;
52769             for (; j < ln2; j++) {
52770                 e = inputList[j];
52771                 if (me.intersectInside(e, cp1, cp2)) {
52772                     if (!me.intersectInside(s, cp1, cp2)) {
52773                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52774                     }
52775                     outputList.push(e);
52776                 }
52777                 else if (me.intersectInside(s, cp1, cp2)) {
52778                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52779                 }
52780                 s = e;
52781             }
52782             cp1 = cp2;
52783         }
52784         return outputList;
52785     },
52786
52787     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
52788         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
52789             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
52790             c = p1x - c1x,
52791             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52792             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52793             y = [p1y, p2y],
52794             x = [p1x, p2x],
52795             dot;
52796         if (Math.abs(t1) > 1e12) {
52797             t1 = 0.5;
52798         }
52799         if (Math.abs(t2) > 1e12) {
52800             t2 = 0.5;
52801         }
52802         if (t1 > 0 && t1 < 1) {
52803             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52804             x.push(dot.x);
52805             y.push(dot.y);
52806         }
52807         if (t2 > 0 && t2 < 1) {
52808             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52809             x.push(dot.x);
52810             y.push(dot.y);
52811         }
52812         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
52813         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
52814         c = p1y - c1y;
52815         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52816         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52817         if (Math.abs(t1) > 1e12) {
52818             t1 = 0.5;
52819         }
52820         if (Math.abs(t2) > 1e12) {
52821             t2 = 0.5;
52822         }
52823         if (t1 > 0 && t1 < 1) {
52824             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52825             x.push(dot.x);
52826             y.push(dot.y);
52827         }
52828         if (t2 > 0 && t2 < 1) {
52829             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52830             x.push(dot.x);
52831             y.push(dot.y);
52832         }
52833         return {
52834             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
52835             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
52836         };
52837     },
52838
52839     /**
52840      * @private
52841      *
52842      * Calculates bezier curve control anchor points for a particular point in a path, with a
52843      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
52844      * Note that this algorithm assumes that the line being smoothed is normalized going from left
52845      * to right; it makes special adjustments assuming this orientation.
52846      *
52847      * @param {Number} prevX X coordinate of the previous point in the path
52848      * @param {Number} prevY Y coordinate of the previous point in the path
52849      * @param {Number} curX X coordinate of the current point in the path
52850      * @param {Number} curY Y coordinate of the current point in the path
52851      * @param {Number} nextX X coordinate of the next point in the path
52852      * @param {Number} nextY Y coordinate of the next point in the path
52853      * @param {Number} value A value to control the smoothness of the curve; this is used to
52854      * divide the distance between points, so a value of 2 corresponds to
52855      * half the distance between points (a very smooth line) while higher values
52856      * result in less smooth curves. Defaults to 4.
52857      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
52858      * are the control point for the curve toward the previous path point, and
52859      * x2 and y2 are the control point for the curve toward the next path point.
52860      */
52861     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
52862         value = value || 4;
52863         var M = Math,
52864             PI = M.PI,
52865             halfPI = PI / 2,
52866             abs = M.abs,
52867             sin = M.sin,
52868             cos = M.cos,
52869             atan = M.atan,
52870             control1Length, control2Length, control1Angle, control2Angle,
52871             control1X, control1Y, control2X, control2Y, alpha;
52872
52873         // Find the length of each control anchor line, by dividing the horizontal distance
52874         // between points by the value parameter.
52875         control1Length = (curX - prevX) / value;
52876         control2Length = (nextX - curX) / value;
52877
52878         // Determine the angle of each control anchor line. If the middle point is a vertical
52879         // turnaround then we force it to a flat horizontal angle to prevent the curve from
52880         // dipping above or below the middle point. Otherwise we use an angle that points
52881         // toward the previous/next target point.
52882         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
52883             control1Angle = control2Angle = halfPI;
52884         } else {
52885             control1Angle = atan((curX - prevX) / abs(curY - prevY));
52886             if (prevY < curY) {
52887                 control1Angle = PI - control1Angle;
52888             }
52889             control2Angle = atan((nextX - curX) / abs(curY - nextY));
52890             if (nextY < curY) {
52891                 control2Angle = PI - control2Angle;
52892             }
52893         }
52894
52895         // Adjust the calculated angles so they point away from each other on the same line
52896         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
52897         if (alpha > halfPI) {
52898             alpha -= PI;
52899         }
52900         control1Angle += alpha;
52901         control2Angle += alpha;
52902
52903         // Find the control anchor points from the angles and length
52904         control1X = curX - control1Length * sin(control1Angle);
52905         control1Y = curY + control1Length * cos(control1Angle);
52906         control2X = curX + control2Length * sin(control2Angle);
52907         control2Y = curY + control2Length * cos(control2Angle);
52908
52909         // One last adjustment, make sure that no control anchor point extends vertically past
52910         // its target prev/next point, as that results in curves dipping above or below and
52911         // bending back strangely. If we find this happening we keep the control angle but
52912         // reduce the length of the control line so it stays within bounds.
52913         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
52914             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
52915             control1Y = prevY;
52916         }
52917         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
52918             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
52919             control2Y = nextY;
52920         }
52921         
52922         return {
52923             x1: control1X,
52924             y1: control1Y,
52925             x2: control2X,
52926             y2: control2Y
52927         };
52928     },
52929
52930     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
52931      * Defaults to a value of 4.
52932      */
52933     smooth: function (originalPath, value) {
52934         var path = this.path2curve(originalPath),
52935             newp = [path[0]],
52936             x = path[0][1],
52937             y = path[0][2],
52938             j,
52939             points,
52940             i = 1,
52941             ii = path.length,
52942             beg = 1,
52943             mx = x,
52944             my = y,
52945             cx = 0,
52946             cy = 0;
52947         for (; i < ii; i++) {
52948             var pathi = path[i],
52949                 pathil = pathi.length,
52950                 pathim = path[i - 1],
52951                 pathiml = pathim.length,
52952                 pathip = path[i + 1],
52953                 pathipl = pathip && pathip.length;
52954             if (pathi[0] == "M") {
52955                 mx = pathi[1];
52956                 my = pathi[2];
52957                 j = i + 1;
52958                 while (path[j][0] != "C") {
52959                     j++;
52960                 }
52961                 cx = path[j][5];
52962                 cy = path[j][6];
52963                 newp.push(["M", mx, my]);
52964                 beg = newp.length;
52965                 x = mx;
52966                 y = my;
52967                 continue;
52968             }
52969             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
52970                 var begl = newp[beg].length;
52971                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
52972                 newp[beg][1] = points.x2;
52973                 newp[beg][2] = points.y2;
52974             }
52975             else if (!pathip || pathip[0] == "M") {
52976                 points = {
52977                     x1: pathi[pathil - 2],
52978                     y1: pathi[pathil - 1]
52979                 };
52980             } else {
52981                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
52982             }
52983             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
52984             x = points.x2;
52985             y = points.y2;
52986         }
52987         return newp;
52988     },
52989
52990     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
52991         var t1 = 1 - t;
52992         return {
52993             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
52994             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
52995         };
52996     },
52997
52998     /**
52999      * A utility method to deduce an appropriate tick configuration for the data set of given
53000      * feature.
53001      * 
53002      * @param {Number/Date} from The minimum value in the data
53003      * @param {Number/Date} to The maximum value in the data
53004      * @param {Number} stepsMax The maximum number of ticks
53005      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
53006      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
53007      * @return {Number} return.from The result start value, which may be lower than the original start value
53008      * @return {Number} return.to The result end value, which may be higher than the original end value
53009      * @return {Number} return.power The calculate power.
53010      * @return {Number} return.step The value size of each step
53011      * @return {Number} return.steps The number of steps.
53012      */
53013     snapEnds: function (from, to, stepsMax) {
53014         if (Ext.isDate(from)) {
53015             return this.snapEndsByDate(from, to, stepsMax);
53016         }
53017         var step = (to - from) / stepsMax,
53018             level = Math.floor(Math.log(step) / Math.LN10) + 1,
53019             m = Math.pow(10, level),
53020             cur,
53021             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
53022             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
53023             stepCount = 0,
53024             value,
53025             weight,
53026             i,
53027             topValue,
53028             topWeight = 1e9,
53029             ln = interval.length;
53030         cur = from = Math.floor(from / m) * m;
53031         for (i = 0; i < ln; i++) {
53032             value = interval[i][0];
53033             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
53034             if (weight < topWeight) {
53035                 topValue = value;
53036                 topWeight = weight;
53037             }
53038         }
53039         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
53040         while (cur < to) {
53041             cur += step;
53042             stepCount++;
53043         }
53044         to = +cur.toFixed(10);
53045         return {
53046             from: from,
53047             to: to,
53048             power: level,
53049             step: step,
53050             steps: stepCount
53051         };
53052     },
53053
53054     /**
53055      * A utility method to deduce an appropriate tick configuration for the data set of given
53056      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
53057      *
53058      * @param {Date} from The minimum value in the data
53059      * @param {Date} to The maximum value in the data
53060      * @param {Number} stepsMax The maximum number of ticks
53061      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53062      * and will not be adjusted
53063      * @return {Object} The calculated step and ends info; properties are:
53064      * @return {Date} return.from The result start value, which may be lower than the original start value
53065      * @return {Date} return.to The result end value, which may be higher than the original end value
53066      * @return {Number} return.step The value size of each step
53067      * @return {Number} return.steps The number of steps.
53068      * NOTE: the steps may not divide the from/to range perfectly evenly;
53069      * there may be a smaller distance between the last step and the end value than between prior
53070      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
53071      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
53072      * `from` to find the correct point for each tick.
53073      */
53074     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
53075         var selectedStep = false, scales = [
53076                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
53077                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
53078                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
53079                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
53080                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
53081                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
53082             ], j, yearDiff;
53083
53084         // Find the most desirable scale
53085         Ext.each(scales, function(scale, i) {
53086             for (j = 0; j < scale[1].length; j++) {
53087                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
53088                     selectedStep = [scale[0], scale[1][j]];
53089                     return false;
53090                 }
53091             }
53092         });
53093         if (!selectedStep) {
53094             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
53095             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
53096         }
53097         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
53098     },
53099
53100
53101     /**
53102      * A utility method to deduce an appropriate tick configuration for the data set of given
53103      * feature and specific step size.
53104      * @param {Date} from The minimum value in the data
53105      * @param {Date} to The maximum value in the data
53106      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
53107      * The second one is the number of units for the step (1, 2, etc.).
53108      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53109      * and will not be adjusted
53110      * @return {Object} See the return value of {@link #snapEndsByDate}.
53111      */
53112     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
53113         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
53114                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
53115             steps = 0, testFrom, testTo;
53116         if (lockEnds) {
53117             testFrom = from;
53118         } else {
53119             switch (step[0]) {
53120                 case Ext.Date.MILLI:
53121                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53122                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
53123                     break;
53124                 case Ext.Date.SECOND:
53125                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53126                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
53127                     break;
53128                 case Ext.Date.MINUTE:
53129                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53130                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
53131                     break;
53132                 case Ext.Date.HOUR:
53133                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
53134                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
53135                     break;
53136                 case Ext.Date.DAY:
53137                     testFrom = new Date(fromStat[0], fromStat[1],
53138                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
53139                     break;
53140                 case Ext.Date.MONTH:
53141                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
53142                     break;
53143                 default: // Ext.Date.YEAR
53144                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
53145                     break;
53146             }
53147         }
53148
53149         testTo = testFrom;
53150         // TODO(zhangbei) : We can do it better somehow...
53151         while (testTo < to) {
53152             testTo = Ext.Date.add(testTo, step[0], step[1]);
53153             steps++;
53154         }
53155
53156         if (lockEnds) {
53157             testTo = to;
53158         }
53159         return {
53160             from : +testFrom,
53161             to : +testTo,
53162             step : (testTo - testFrom) / steps,
53163             steps : steps
53164         };
53165     },
53166
53167     sorter: function (a, b) {
53168         return a.offset - b.offset;
53169     },
53170
53171     rad: function(degrees) {
53172         return degrees % 360 * Math.PI / 180;
53173     },
53174
53175     degrees: function(radian) {
53176         return radian * 180 / Math.PI % 360;
53177     },
53178
53179     withinBox: function(x, y, bbox) {
53180         bbox = bbox || {};
53181         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
53182     },
53183
53184     parseGradient: function(gradient) {
53185         var me = this,
53186             type = gradient.type || 'linear',
53187             angle = gradient.angle || 0,
53188             radian = me.radian,
53189             stops = gradient.stops,
53190             stopsArr = [],
53191             stop,
53192             vector,
53193             max,
53194             stopObj;
53195
53196         if (type == 'linear') {
53197             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
53198             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
53199             vector[2] *= max;
53200             vector[3] *= max;
53201             if (vector[2] < 0) {
53202                 vector[0] = -vector[2];
53203                 vector[2] = 0;
53204             }
53205             if (vector[3] < 0) {
53206                 vector[1] = -vector[3];
53207                 vector[3] = 0;
53208             }
53209         }
53210
53211         for (stop in stops) {
53212             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
53213                 stopObj = {
53214                     offset: parseInt(stop, 10),
53215                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
53216                     opacity: stops[stop].opacity || 1
53217                 };
53218                 stopsArr.push(stopObj);
53219             }
53220         }
53221         // Sort by pct property
53222         Ext.Array.sort(stopsArr, me.sorter);
53223         if (type == 'linear') {
53224             return {
53225                 id: gradient.id,
53226                 type: type,
53227                 vector: vector,
53228                 stops: stopsArr
53229             };
53230         }
53231         else {
53232             return {
53233                 id: gradient.id,
53234                 type: type,
53235                 centerX: gradient.centerX,
53236                 centerY: gradient.centerY,
53237                 focalX: gradient.focalX,
53238                 focalY: gradient.focalY,
53239                 radius: gradient.radius,
53240                 vector: vector,
53241                 stops: stopsArr
53242             };
53243         }
53244     }
53245 });
53246
53247
53248 /**
53249  * @class Ext.fx.PropertyHandler
53250  * @ignore
53251  */
53252 Ext.define('Ext.fx.PropertyHandler', {
53253
53254     /* Begin Definitions */
53255
53256     requires: ['Ext.draw.Draw'],
53257
53258     statics: {
53259         defaultHandler: {
53260             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
53261             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
53262             scrollRE: /^scroll/i,
53263
53264             computeDelta: function(from, end, damper, initial, attr) {
53265                 damper = (typeof damper == 'number') ? damper : 1;
53266                 var unitRE = this.unitRE,
53267                     match = unitRE.exec(from),
53268                     start, units;
53269                 if (match) {
53270                     from = match[1];
53271                     units = match[2];
53272                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
53273                         units = 'px';
53274                     }
53275                 }
53276                 from = +from || 0;
53277
53278                 match = unitRE.exec(end);
53279                 if (match) {
53280                     end = match[1];
53281                     units = match[2] || units;
53282                 }
53283                 end = +end || 0;
53284                 start = (initial != null) ? initial : from;
53285                 return {
53286                     from: from,
53287                     delta: (end - start) * damper,
53288                     units: units
53289                 };
53290             },
53291
53292             get: function(from, end, damper, initialFrom, attr) {
53293                 var ln = from.length,
53294                     out = [],
53295                     i, initial, res, j, len;
53296                 for (i = 0; i < ln; i++) {
53297                     if (initialFrom) {
53298                         initial = initialFrom[i][1].from;
53299                     }
53300                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
53301                         res = [];
53302                         j = 0;
53303                         len = from[i][1].length;
53304                         for (; j < len; j++) {
53305                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
53306                         }
53307                         out.push([from[i][0], res]);
53308                     }
53309                     else {
53310                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
53311                     }
53312                 }
53313                 return out;
53314             },
53315
53316             set: function(values, easing) {
53317                 var ln = values.length,
53318                     out = [],
53319                     i, val, res, len, j;
53320                 for (i = 0; i < ln; i++) {
53321                     val  = values[i][1];
53322                     if (Ext.isArray(val)) {
53323                         res = [];
53324                         j = 0;
53325                         len = val.length;
53326                         for (; j < len; j++) {
53327                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
53328                         }
53329                         out.push([values[i][0], res]);
53330                     } else {
53331                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
53332                     }
53333                 }
53334                 return out;
53335             }
53336         },
53337         color: {
53338             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
53339             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
53340             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
53341
53342             parseColor : function(color, damper) {
53343                 damper = (typeof damper == 'number') ? damper : 1;
53344                 var base,
53345                     out = false,
53346                     match;
53347
53348                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
53349                     base = (idx % 2 == 0) ? 16 : 10;
53350                     match = re.exec(color);
53351                     if (match && match.length == 4) {
53352                         if (idx == 2) {
53353                             match[1] += match[1];
53354                             match[2] += match[2];
53355                             match[3] += match[3];
53356                         }
53357                         out = {
53358                             red: parseInt(match[1], base),
53359                             green: parseInt(match[2], base),
53360                             blue: parseInt(match[3], base)
53361                         };
53362                         return false;
53363                     }
53364                 });
53365                 return out || color;
53366             },
53367
53368             computeDelta: function(from, end, damper, initial) {
53369                 from = this.parseColor(from);
53370                 end = this.parseColor(end, damper);
53371                 var start = initial ? initial : from,
53372                     tfrom = typeof start,
53373                     tend = typeof end;
53374                 //Extra check for when the color string is not recognized.
53375                 if (tfrom == 'string' ||  tfrom == 'undefined' 
53376                   || tend == 'string' || tend == 'undefined') {
53377                     return end || start;
53378                 }
53379                 return {
53380                     from:  from,
53381                     delta: {
53382                         red: Math.round((end.red - start.red) * damper),
53383                         green: Math.round((end.green - start.green) * damper),
53384                         blue: Math.round((end.blue - start.blue) * damper)
53385                     }
53386                 };
53387             },
53388
53389             get: function(start, end, damper, initialFrom) {
53390                 var ln = start.length,
53391                     out = [],
53392                     i, initial;
53393                 for (i = 0; i < ln; i++) {
53394                     if (initialFrom) {
53395                         initial = initialFrom[i][1].from;
53396                     }
53397                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53398                 }
53399                 return out;
53400             },
53401
53402             set: function(values, easing) {
53403                 var ln = values.length,
53404                     out = [],
53405                     i, val, parsedString, from, delta;
53406                 for (i = 0; i < ln; i++) {
53407                     val = values[i][1];
53408                     if (val) {
53409                         from = val.from;
53410                         delta = val.delta;
53411                         //multiple checks to reformat the color if it can't recognized by computeDelta.
53412                         val = (typeof val == 'object' && 'red' in val)? 
53413                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
53414                         val = (typeof val == 'object' && val.length)? val[0] : val;
53415                         if (typeof val == 'undefined') {
53416                             return [];
53417                         }
53418                         parsedString = typeof val == 'string'? val :
53419                             'rgb(' + [
53420                                   (from.red + Math.round(delta.red * easing)) % 256,
53421                                   (from.green + Math.round(delta.green * easing)) % 256,
53422                                   (from.blue + Math.round(delta.blue * easing)) % 256
53423                               ].join(',') + ')';
53424                         out.push([
53425                             values[i][0],
53426                             parsedString
53427                         ]);
53428                     }
53429                 }
53430                 return out;
53431             }
53432         },
53433         object: {
53434             interpolate: function(prop, damper) {
53435                 damper = (typeof damper == 'number') ? damper : 1;
53436                 var out = {},
53437                     p;
53438                 for(p in prop) {
53439                     out[p] = parseInt(prop[p], 10) * damper;
53440                 }
53441                 return out;
53442             },
53443
53444             computeDelta: function(from, end, damper, initial) {
53445                 from = this.interpolate(from);
53446                 end = this.interpolate(end, damper);
53447                 var start = initial ? initial : from,
53448                     delta = {},
53449                     p;
53450
53451                 for(p in end) {
53452                     delta[p] = end[p] - start[p];
53453                 }
53454                 return {
53455                     from:  from,
53456                     delta: delta
53457                 };
53458             },
53459
53460             get: function(start, end, damper, initialFrom) {
53461                 var ln = start.length,
53462                     out = [],
53463                     i, initial;
53464                 for (i = 0; i < ln; i++) {
53465                     if (initialFrom) {
53466                         initial = initialFrom[i][1].from;
53467                     }
53468                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53469                 }
53470                 return out;
53471             },
53472
53473             set: function(values, easing) {
53474                 var ln = values.length,
53475                     out = [],
53476                     outObject = {},
53477                     i, from, delta, val, p;
53478                 for (i = 0; i < ln; i++) {
53479                     val  = values[i][1];
53480                     from = val.from;
53481                     delta = val.delta;
53482                     for (p in from) {
53483                         outObject[p] = Math.round(from[p] + delta[p] * easing);
53484                     }
53485                     out.push([
53486                         values[i][0],
53487                         outObject
53488                     ]);
53489                 }
53490                 return out;
53491             }
53492         },
53493
53494         path: {
53495             computeDelta: function(from, end, damper, initial) {
53496                 damper = (typeof damper == 'number') ? damper : 1;
53497                 var start;
53498                 from = +from || 0;
53499                 end = +end || 0;
53500                 start = (initial != null) ? initial : from;
53501                 return {
53502                     from: from,
53503                     delta: (end - start) * damper
53504                 };
53505             },
53506
53507             forcePath: function(path) {
53508                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
53509                     path = Ext.draw.Draw.parsePathString(path);
53510                 }
53511                 return path;
53512             },
53513
53514             get: function(start, end, damper, initialFrom) {
53515                 var endPath = this.forcePath(end),
53516                     out = [],
53517                     startLn = start.length,
53518                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
53519                 for (i = 0; i < startLn; i++) {
53520                     startPath = this.forcePath(start[i][1]);
53521
53522                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
53523                     startPath = deltaPath[0];
53524                     endPath = deltaPath[1];
53525
53526                     startPathLn = startPath.length;
53527                     path = [];
53528                     for (j = 0; j < startPathLn; j++) {
53529                         deltaPath = [startPath[j][0]];
53530                         pointsLn = startPath[j].length;
53531                         for (k = 1; k < pointsLn; k++) {
53532                             initial = initialFrom && initialFrom[0][1][j][k].from;
53533                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
53534                         }
53535                         path.push(deltaPath);
53536                     }
53537                     out.push([start[i][0], path]);
53538                 }
53539                 return out;
53540             },
53541
53542             set: function(values, easing) {
53543                 var ln = values.length,
53544                     out = [],
53545                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
53546                 for (i = 0; i < ln; i++) {
53547                     deltaPath = values[i][1];
53548                     newPath = [];
53549                     deltaPathLn = deltaPath.length;
53550                     for (j = 0; j < deltaPathLn; j++) {
53551                         calcPath = [deltaPath[j][0]];
53552                         pointsLn = deltaPath[j].length;
53553                         for (k = 1; k < pointsLn; k++) {
53554                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
53555                         }
53556                         newPath.push(calcPath.join(','));
53557                     }
53558                     out.push([values[i][0], newPath.join(',')]);
53559                 }
53560                 return out;
53561             }
53562         }
53563         /* End Definitions */
53564     }
53565 }, function() {
53566     Ext.each([
53567         'outlineColor',
53568         'backgroundColor',
53569         'borderColor',
53570         'borderTopColor',
53571         'borderRightColor', 
53572         'borderBottomColor', 
53573         'borderLeftColor',
53574         'fill',
53575         'stroke'
53576     ], function(prop) {
53577         this[prop] = this.color;
53578     }, this);
53579 });
53580 /**
53581  * @class Ext.fx.Anim
53582  *
53583  * This class manages animation for a specific {@link #target}. The animation allows
53584  * animation of various properties on the target, such as size, position, color and others.
53585  *
53586  * ## Starting Conditions
53587  * The starting conditions for the animation are provided by the {@link #from} configuration.
53588  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
53589  * property is not defined, the starting value for that property will be read directly from the target.
53590  *
53591  * ## End Conditions
53592  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
53593  * the final values once the animations has finished. The values in the {@link #from} can mirror
53594  * those in the {@link #to} configuration to provide a starting point.
53595  *
53596  * ## Other Options
53597  *  - {@link #duration}: Specifies the time period of the animation.
53598  *  - {@link #easing}: Specifies the easing of the animation.
53599  *  - {@link #iterations}: Allows the animation to repeat a number of times.
53600  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
53601  *
53602  * ## Example Code
53603  *
53604  *     @example
53605  *     var myComponent = Ext.create('Ext.Component', {
53606  *         renderTo: document.body,
53607  *         width: 200,
53608  *         height: 200,
53609  *         style: 'border: 1px solid red;'
53610  *     });
53611  *
53612  *     Ext.create('Ext.fx.Anim', {
53613  *         target: myComponent,
53614  *         duration: 1000,
53615  *         from: {
53616  *             width: 400 //starting width 400
53617  *         },
53618  *         to: {
53619  *             width: 300, //end width 300
53620  *             height: 300 // end width 300
53621  *         }
53622  *     });
53623  */
53624 Ext.define('Ext.fx.Anim', {
53625
53626     /* Begin Definitions */
53627
53628     mixins: {
53629         observable: 'Ext.util.Observable'
53630     },
53631
53632     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
53633
53634     /* End Definitions */
53635
53636     isAnimation: true,
53637
53638     /**
53639      * @cfg {Function} callback
53640      * A function to be run after the animation has completed.
53641      */
53642
53643     /**
53644      * @cfg {Function} scope
53645      * The scope that the {@link #callback} function will be called with
53646      */
53647
53648     /**
53649      * @cfg {Number} duration
53650      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
53651      * specified, then each animate will take the same duration for each iteration.
53652      */
53653     duration: 250,
53654
53655     /**
53656      * @cfg {Number} delay
53657      * Time to delay before starting the animation. Defaults to 0.
53658      */
53659     delay: 0,
53660
53661     /* private used to track a delayed starting time */
53662     delayStart: 0,
53663
53664     /**
53665      * @cfg {Boolean} dynamic
53666      * 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.
53667      */
53668     dynamic: false,
53669
53670     /**
53671      * @cfg {String} easing
53672 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
53673 speed over its duration.
53674
53675          -backIn
53676          -backOut
53677          -bounceIn
53678          -bounceOut
53679          -ease
53680          -easeIn
53681          -easeOut
53682          -easeInOut
53683          -elasticIn
53684          -elasticOut
53685          -cubic-bezier(x1, y1, x2, y2)
53686
53687 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
53688 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
53689 be in the range [0, 1] or the definition is invalid.
53690
53691 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
53692
53693      * @markdown
53694      */
53695     easing: 'ease',
53696
53697      /**
53698       * @cfg {Object} keyframes
53699       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
53700       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
53701       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
53702       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
53703       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
53704  <pre><code>
53705 keyframes : {
53706     '0%': {
53707         left: 100
53708     },
53709     '40%': {
53710         left: 150
53711     },
53712     '60%': {
53713         left: 75
53714     },
53715     '100%': {
53716         left: 100
53717     }
53718 }
53719  </code></pre>
53720       */
53721
53722     /**
53723      * @private
53724      */
53725     damper: 1,
53726
53727     /**
53728      * @private
53729      */
53730     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
53731
53732     /**
53733      * Run the animation from the end to the beginning
53734      * Defaults to false.
53735      * @cfg {Boolean} reverse
53736      */
53737     reverse: false,
53738
53739     /**
53740      * Flag to determine if the animation has started
53741      * @property running
53742      * @type Boolean
53743      */
53744     running: false,
53745
53746     /**
53747      * Flag to determine if the animation is paused. Only set this to true if you need to
53748      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
53749      * @property paused
53750      * @type Boolean
53751      */
53752     paused: false,
53753
53754     /**
53755      * Number of times to execute the animation. Defaults to 1.
53756      * @cfg {Number} iterations
53757      */
53758     iterations: 1,
53759
53760     /**
53761      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
53762      * @cfg {Boolean} alternate
53763      * Defaults to false.
53764      */
53765     alternate: false,
53766
53767     /**
53768      * Current iteration the animation is running.
53769      * @property currentIteration
53770      * @type Number
53771      */
53772     currentIteration: 0,
53773
53774     /**
53775      * Starting time of the animation.
53776      * @property startTime
53777      * @type Date
53778      */
53779     startTime: 0,
53780
53781     /**
53782      * Contains a cache of the interpolators to be used.
53783      * @private
53784      * @property propHandlers
53785      * @type Object
53786      */
53787
53788     /**
53789      * @cfg {String/Object} target
53790      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
53791      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
53792      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
53793      * automatically.
53794      */
53795
53796     /**
53797      * @cfg {Object} from
53798      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
53799      * Ext.fx.target will be used. For example:
53800 <pre><code>
53801 from : {
53802     opacity: 0,       // Transparent
53803     color: '#ffffff', // White
53804     left: 0
53805 }
53806 </code></pre>
53807      */
53808
53809     /**
53810      * @cfg {Object} to
53811      * An object containing property/value pairs for the end of the animation. For example:
53812  <pre><code>
53813  to : {
53814      opacity: 1,       // Opaque
53815      color: '#00ff00', // Green
53816      left: 500
53817  }
53818  </code></pre>
53819      */
53820
53821     // @private
53822     constructor: function(config) {
53823         var me = this,
53824             curve;
53825             
53826         config = config || {};
53827         // If keyframes are passed, they really want an Animator instead.
53828         if (config.keyframes) {
53829             return Ext.create('Ext.fx.Animator', config);
53830         }
53831         config = Ext.apply(me, config);
53832         if (me.from === undefined) {
53833             me.from = {};
53834         }
53835         me.propHandlers = {};
53836         me.config = config;
53837         me.target = Ext.fx.Manager.createTarget(me.target);
53838         me.easingFn = Ext.fx.Easing[me.easing];
53839         me.target.dynamic = me.dynamic;
53840
53841         // If not a pre-defined curve, try a cubic-bezier
53842         if (!me.easingFn) {
53843             me.easingFn = String(me.easing).match(me.bezierRE);
53844             if (me.easingFn && me.easingFn.length == 5) {
53845                 curve = me.easingFn;
53846                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
53847             }
53848         }
53849         me.id = Ext.id(null, 'ext-anim-');
53850         Ext.fx.Manager.addAnim(me);
53851         me.addEvents(
53852             /**
53853              * @event beforeanimate
53854              * Fires before the animation starts. A handler can return false to cancel the animation.
53855              * @param {Ext.fx.Anim} this
53856              */
53857             'beforeanimate',
53858              /**
53859               * @event afteranimate
53860               * Fires when the animation is complete.
53861               * @param {Ext.fx.Anim} this
53862               * @param {Date} startTime
53863               */
53864             'afteranimate',
53865              /**
53866               * @event lastframe
53867               * Fires when the animation's last frame has been set.
53868               * @param {Ext.fx.Anim} this
53869               * @param {Date} startTime
53870               */
53871             'lastframe'
53872         );
53873         me.mixins.observable.constructor.call(me, config);
53874         if (config.callback) {
53875             me.on('afteranimate', config.callback, config.scope);
53876         }
53877         return me;
53878     },
53879
53880     /**
53881      * @private
53882      * Helper to the target
53883      */
53884     setAttr: function(attr, value) {
53885         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
53886     },
53887
53888     /**
53889      * @private
53890      * Set up the initial currentAttrs hash.
53891      */
53892     initAttrs: function() {
53893         var me = this,
53894             from = me.from,
53895             to = me.to,
53896             initialFrom = me.initialFrom || {},
53897             out = {},
53898             start, end, propHandler, attr;
53899
53900         for (attr in to) {
53901             if (to.hasOwnProperty(attr)) {
53902                 start = me.target.getAttr(attr, from[attr]);
53903                 end = to[attr];
53904                 // Use default (numeric) property handler
53905                 if (!Ext.fx.PropertyHandler[attr]) {
53906                     if (Ext.isObject(end)) {
53907                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
53908                     } else {
53909                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
53910                     }
53911                 }
53912                 // Use custom handler
53913                 else {
53914                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
53915                 }
53916                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
53917             }
53918         }
53919         me.currentAttrs = out;
53920     },
53921
53922     /**
53923      * @private
53924      * Fires beforeanimate and sets the running flag.
53925      */
53926     start: function(startTime) {
53927         var me = this,
53928             delay = me.delay,
53929             delayStart = me.delayStart,
53930             delayDelta;
53931         if (delay) {
53932             if (!delayStart) {
53933                 me.delayStart = startTime;
53934                 return;
53935             }
53936             else {
53937                 delayDelta = startTime - delayStart;
53938                 if (delayDelta < delay) {
53939                     return;
53940                 }
53941                 else {
53942                     // Compensate for frame delay;
53943                     startTime = new Date(delayStart.getTime() + delay);
53944                 }
53945             }
53946         }
53947         if (me.fireEvent('beforeanimate', me) !== false) {
53948             me.startTime = startTime;
53949             if (!me.paused && !me.currentAttrs) {
53950                 me.initAttrs();
53951             }
53952             me.running = true;
53953         }
53954     },
53955
53956     /**
53957      * @private
53958      * Calculate attribute value at the passed timestamp.
53959      * @returns a hash of the new attributes.
53960      */
53961     runAnim: function(elapsedTime) {
53962         var me = this,
53963             attrs = me.currentAttrs,
53964             duration = me.duration,
53965             easingFn = me.easingFn,
53966             propHandlers = me.propHandlers,
53967             ret = {},
53968             easing, values, attr, lastFrame;
53969
53970         if (elapsedTime >= duration) {
53971             elapsedTime = duration;
53972             lastFrame = true;
53973         }
53974         if (me.reverse) {
53975             elapsedTime = duration - elapsedTime;
53976         }
53977
53978         for (attr in attrs) {
53979             if (attrs.hasOwnProperty(attr)) {
53980                 values = attrs[attr];
53981                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
53982                 ret[attr] = propHandlers[attr].set(values, easing);
53983             }
53984         }
53985         return ret;
53986     },
53987
53988     /**
53989      * @private
53990      * Perform lastFrame cleanup and handle iterations
53991      * @returns a hash of the new attributes.
53992      */
53993     lastFrame: function() {
53994         var me = this,
53995             iter = me.iterations,
53996             iterCount = me.currentIteration;
53997
53998         iterCount++;
53999         if (iterCount < iter) {
54000             if (me.alternate) {
54001                 me.reverse = !me.reverse;
54002             }
54003             me.startTime = new Date();
54004             me.currentIteration = iterCount;
54005             // Turn off paused for CSS3 Transitions
54006             me.paused = false;
54007         }
54008         else {
54009             me.currentIteration = 0;
54010             me.end();
54011             me.fireEvent('lastframe', me, me.startTime);
54012         }
54013     },
54014
54015     /**
54016      * Fire afteranimate event and end the animation. Usually called automatically when the
54017      * animation reaches its final frame, but can also be called manually to pre-emptively
54018      * stop and destroy the running animation.
54019      */
54020     end: function() {
54021         var me = this;
54022         me.startTime = 0;
54023         me.paused = false;
54024         me.running = false;
54025         Ext.fx.Manager.removeAnim(me);
54026         me.fireEvent('afteranimate', me, me.startTime);
54027     }
54028 });
54029 // Set flag to indicate that Fx is available. Class might not be available immediately.
54030 Ext.enableFx = true;
54031
54032 /*
54033  * This is a derivative of the similarly named class in the YUI Library.
54034  * The original license:
54035  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
54036  * Code licensed under the BSD License:
54037  * http://developer.yahoo.net/yui/license.txt
54038  */
54039
54040
54041 /**
54042  * Defines the interface and base operation of items that that can be
54043  * dragged or can be drop targets.  It was designed to be extended, overriding
54044  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
54045  * Up to three html elements can be associated with a DragDrop instance:
54046  *
54047  * - linked element: the element that is passed into the constructor.
54048  *   This is the element which defines the boundaries for interaction with
54049  *   other DragDrop objects.
54050  *
54051  * - handle element(s): The drag operation only occurs if the element that
54052  *   was clicked matches a handle element.  By default this is the linked
54053  *   element, but there are times that you will want only a portion of the
54054  *   linked element to initiate the drag operation, and the setHandleElId()
54055  *   method provides a way to define this.
54056  *
54057  * - drag element: this represents the element that would be moved along
54058  *   with the cursor during a drag operation.  By default, this is the linked
54059  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
54060  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
54061  *
54062  * This class should not be instantiated until the onload event to ensure that
54063  * the associated elements are available.
54064  * The following would define a DragDrop obj that would interact with any
54065  * other DragDrop obj in the "group1" group:
54066  *
54067  *     dd = new Ext.dd.DragDrop("div1", "group1");
54068  *
54069  * Since none of the event handlers have been implemented, nothing would
54070  * actually happen if you were to run the code above.  Normally you would
54071  * override this class or one of the default implementations, but you can
54072  * also override the methods you want on an instance of the class...
54073  *
54074  *     dd.onDragDrop = function(e, id) {
54075  *         alert("dd was dropped on " + id);
54076  *     }
54077  *
54078  */
54079 Ext.define('Ext.dd.DragDrop', {
54080     requires: ['Ext.dd.DragDropManager'],
54081
54082     /**
54083      * Creates new DragDrop.
54084      * @param {String} id of the element that is linked to this instance
54085      * @param {String} sGroup the group of related DragDrop objects
54086      * @param {Object} config an object containing configurable attributes.
54087      * Valid properties for DragDrop:
54088      *
54089      * - padding
54090      * - isTarget
54091      * - maintainOffset
54092      * - primaryButtonOnly
54093      */
54094     constructor: function(id, sGroup, config) {
54095         if(id) {
54096             this.init(id, sGroup, config);
54097         }
54098     },
54099
54100     /**
54101      * Set to false to enable a DragDrop object to fire drag events while dragging
54102      * over its own Element. Defaults to true - DragDrop objects do not by default
54103      * fire drag events to themselves.
54104      * @property ignoreSelf
54105      * @type Boolean
54106      */
54107
54108     /**
54109      * The id of the element associated with this object.  This is what we
54110      * refer to as the "linked element" because the size and position of
54111      * this element is used to determine when the drag and drop objects have
54112      * interacted.
54113      * @property id
54114      * @type String
54115      */
54116     id: null,
54117
54118     /**
54119      * Configuration attributes passed into the constructor
54120      * @property config
54121      * @type Object
54122      */
54123     config: null,
54124
54125     /**
54126      * The id of the element that will be dragged.  By default this is same
54127      * as the linked element, but could be changed to another element. Ex:
54128      * Ext.dd.DDProxy
54129      * @property dragElId
54130      * @type String
54131      * @private
54132      */
54133     dragElId: null,
54134
54135     /**
54136      * The ID of the element that initiates the drag operation.  By default
54137      * this is the linked element, but could be changed to be a child of this
54138      * element.  This lets us do things like only starting the drag when the
54139      * header element within the linked html element is clicked.
54140      * @property handleElId
54141      * @type String
54142      * @private
54143      */
54144     handleElId: null,
54145
54146     /**
54147      * An object who's property names identify HTML tags to be considered invalid as drag handles.
54148      * A non-null property value identifies the tag as invalid. Defaults to the
54149      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
54150 {
54151     A: "A"
54152 }</code></pre>
54153      * @property invalidHandleTypes
54154      * @type Object
54155      */
54156     invalidHandleTypes: null,
54157
54158     /**
54159      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
54160      * A non-null property value identifies the ID as invalid. For example, to prevent
54161      * dragging from being initiated on element ID "foo", use:<pre><code>
54162 {
54163     foo: true
54164 }</code></pre>
54165      * @property invalidHandleIds
54166      * @type Object
54167      */
54168     invalidHandleIds: null,
54169
54170     /**
54171      * An Array of CSS class names for elements to be considered in valid as drag handles.
54172      * @property {String[]} invalidHandleClasses
54173      */
54174     invalidHandleClasses: null,
54175
54176     /**
54177      * The linked element's absolute X position at the time the drag was
54178      * started
54179      * @property startPageX
54180      * @type Number
54181      * @private
54182      */
54183     startPageX: 0,
54184
54185     /**
54186      * The linked element's absolute X position at the time the drag was
54187      * started
54188      * @property startPageY
54189      * @type Number
54190      * @private
54191      */
54192     startPageY: 0,
54193
54194     /**
54195      * The group defines a logical collection of DragDrop objects that are
54196      * related.  Instances only get events when interacting with other
54197      * DragDrop object in the same group.  This lets us define multiple
54198      * groups using a single DragDrop subclass if we want.
54199      * @property groups
54200      * @type Object An object in the format {'group1':true, 'group2':true}
54201      */
54202     groups: null,
54203
54204     /**
54205      * Individual drag/drop instances can be locked.  This will prevent
54206      * onmousedown start drag.
54207      * @property locked
54208      * @type Boolean
54209      * @private
54210      */
54211     locked: false,
54212
54213     /**
54214      * Locks this instance
54215      */
54216     lock: function() {
54217         this.locked = true;
54218     },
54219
54220     /**
54221      * When set to true, other DD objects in cooperating DDGroups do not receive
54222      * notification events when this DD object is dragged over them. Defaults to false.
54223      * @property moveOnly
54224      * @type Boolean
54225      */
54226     moveOnly: false,
54227
54228     /**
54229      * Unlocks this instace
54230      */
54231     unlock: function() {
54232         this.locked = false;
54233     },
54234
54235     /**
54236      * By default, all instances can be a drop target.  This can be disabled by
54237      * setting isTarget to false.
54238      * @property isTarget
54239      * @type Boolean
54240      */
54241     isTarget: true,
54242
54243     /**
54244      * The padding configured for this drag and drop object for calculating
54245      * the drop zone intersection with this object.
54246      * An array containing the 4 padding values: [top, right, bottom, left]
54247      * @property {Number[]} padding
54248      */
54249     padding: null,
54250
54251     /**
54252      * Cached reference to the linked element
54253      * @property _domRef
54254      * @private
54255      */
54256     _domRef: null,
54257
54258     /**
54259      * Internal typeof flag
54260      * @property __ygDragDrop
54261      * @private
54262      */
54263     __ygDragDrop: true,
54264
54265     /**
54266      * Set to true when horizontal contraints are applied
54267      * @property constrainX
54268      * @type Boolean
54269      * @private
54270      */
54271     constrainX: false,
54272
54273     /**
54274      * Set to true when vertical contraints are applied
54275      * @property constrainY
54276      * @type Boolean
54277      * @private
54278      */
54279     constrainY: false,
54280
54281     /**
54282      * The left constraint
54283      * @property minX
54284      * @type Number
54285      * @private
54286      */
54287     minX: 0,
54288
54289     /**
54290      * The right constraint
54291      * @property maxX
54292      * @type Number
54293      * @private
54294      */
54295     maxX: 0,
54296
54297     /**
54298      * The up constraint
54299      * @property minY
54300      * @type Number
54301      * @private
54302      */
54303     minY: 0,
54304
54305     /**
54306      * The down constraint
54307      * @property maxY
54308      * @type Number
54309      * @private
54310      */
54311     maxY: 0,
54312
54313     /**
54314      * Maintain offsets when we resetconstraints.  Set to true when you want
54315      * the position of the element relative to its parent to stay the same
54316      * when the page changes
54317      *
54318      * @property maintainOffset
54319      * @type Boolean
54320      */
54321     maintainOffset: false,
54322
54323     /**
54324      * Array of pixel locations the element will snap to if we specified a
54325      * horizontal graduation/interval.  This array is generated automatically
54326      * when you define a tick interval.
54327      * @property {Number[]} xTicks
54328      */
54329     xTicks: null,
54330
54331     /**
54332      * Array of pixel locations the element will snap to if we specified a
54333      * vertical graduation/interval.  This array is generated automatically
54334      * when you define a tick interval.
54335      * @property {Number[]} yTicks
54336      */
54337     yTicks: null,
54338
54339     /**
54340      * By default the drag and drop instance will only respond to the primary
54341      * button click (left button for a right-handed mouse).  Set to true to
54342      * allow drag and drop to start with any mouse click that is propogated
54343      * by the browser
54344      * @property primaryButtonOnly
54345      * @type Boolean
54346      */
54347     primaryButtonOnly: true,
54348
54349     /**
54350      * The available property is false until the linked dom element is accessible.
54351      * @property available
54352      * @type Boolean
54353      */
54354     available: false,
54355
54356     /**
54357      * By default, drags can only be initiated if the mousedown occurs in the
54358      * region the linked element is.  This is done in part to work around a
54359      * bug in some browsers that mis-report the mousedown if the previous
54360      * mouseup happened outside of the window.  This property is set to true
54361      * if outer handles are defined. Defaults to false.
54362      *
54363      * @property hasOuterHandles
54364      * @type Boolean
54365      */
54366     hasOuterHandles: false,
54367
54368     /**
54369      * Code that executes immediately before the startDrag event
54370      * @private
54371      */
54372     b4StartDrag: function(x, y) { },
54373
54374     /**
54375      * Abstract method called after a drag/drop object is clicked
54376      * and the drag or mousedown time thresholds have beeen met.
54377      * @param {Number} X click location
54378      * @param {Number} Y click location
54379      */
54380     startDrag: function(x, y) { /* override this */ },
54381
54382     /**
54383      * Code that executes immediately before the onDrag event
54384      * @private
54385      */
54386     b4Drag: function(e) { },
54387
54388     /**
54389      * Abstract method called during the onMouseMove event while dragging an
54390      * object.
54391      * @param {Event} e the mousemove event
54392      */
54393     onDrag: function(e) { /* override this */ },
54394
54395     /**
54396      * Abstract method called when this element fist begins hovering over
54397      * another DragDrop obj
54398      * @param {Event} e the mousemove event
54399      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54400      * id this is hovering over.  In INTERSECT mode, an array of one or more
54401      * dragdrop items being hovered over.
54402      */
54403     onDragEnter: function(e, id) { /* override this */ },
54404
54405     /**
54406      * Code that executes immediately before the onDragOver event
54407      * @private
54408      */
54409     b4DragOver: function(e) { },
54410
54411     /**
54412      * Abstract method called when this element is hovering over another
54413      * DragDrop obj
54414      * @param {Event} e the mousemove event
54415      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54416      * id this is hovering over.  In INTERSECT mode, an array of dd items
54417      * being hovered over.
54418      */
54419     onDragOver: function(e, id) { /* override this */ },
54420
54421     /**
54422      * Code that executes immediately before the onDragOut event
54423      * @private
54424      */
54425     b4DragOut: function(e) { },
54426
54427     /**
54428      * Abstract method called when we are no longer hovering over an element
54429      * @param {Event} e the mousemove event
54430      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54431      * id this was hovering over.  In INTERSECT mode, an array of dd items
54432      * that the mouse is no longer over.
54433      */
54434     onDragOut: function(e, id) { /* override this */ },
54435
54436     /**
54437      * Code that executes immediately before the onDragDrop event
54438      * @private
54439      */
54440     b4DragDrop: function(e) { },
54441
54442     /**
54443      * Abstract method called when this item is dropped on another DragDrop
54444      * obj
54445      * @param {Event} e the mouseup event
54446      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54447      * id this was dropped on.  In INTERSECT mode, an array of dd items this
54448      * was dropped on.
54449      */
54450     onDragDrop: function(e, id) { /* override this */ },
54451
54452     /**
54453      * Abstract method called when this item is dropped on an area with no
54454      * drop target
54455      * @param {Event} e the mouseup event
54456      */
54457     onInvalidDrop: function(e) { /* override this */ },
54458
54459     /**
54460      * Code that executes immediately before the endDrag event
54461      * @private
54462      */
54463     b4EndDrag: function(e) { },
54464
54465     /**
54466      * Called when we are done dragging the object
54467      * @param {Event} e the mouseup event
54468      */
54469     endDrag: function(e) { /* override this */ },
54470
54471     /**
54472      * Code executed immediately before the onMouseDown event
54473      * @param {Event} e the mousedown event
54474      * @private
54475      */
54476     b4MouseDown: function(e) {  },
54477
54478     /**
54479      * Called when a drag/drop obj gets a mousedown
54480      * @param {Event} e the mousedown event
54481      */
54482     onMouseDown: function(e) { /* override this */ },
54483
54484     /**
54485      * Called when a drag/drop obj gets a mouseup
54486      * @param {Event} e the mouseup event
54487      */
54488     onMouseUp: function(e) { /* override this */ },
54489
54490     /**
54491      * Override the onAvailable method to do what is needed after the initial
54492      * position was determined.
54493      */
54494     onAvailable: function () {
54495     },
54496
54497     /**
54498      * @property {Object} defaultPadding
54499      * Provides default constraint padding to "constrainTo" elements.
54500      */
54501     defaultPadding: {
54502         left: 0,
54503         right: 0,
54504         top: 0,
54505         bottom: 0
54506     },
54507
54508     /**
54509      * Initializes the drag drop object's constraints to restrict movement to a certain element.
54510      *
54511      * Usage:
54512      *
54513      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
54514      *                    { dragElId: "existingProxyDiv" });
54515      *     dd.startDrag = function(){
54516      *         this.constrainTo("parent-id");
54517      *     };
54518      *
54519      * Or you can initalize it using the {@link Ext.Element} object:
54520      *
54521      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
54522      *         startDrag : function(){
54523      *             this.constrainTo("parent-id");
54524      *         }
54525      *     });
54526      *
54527      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
54528      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
54529      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
54530      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
54531      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
54532      */
54533     constrainTo : function(constrainTo, pad, inContent){
54534         if(Ext.isNumber(pad)){
54535             pad = {left: pad, right:pad, top:pad, bottom:pad};
54536         }
54537         pad = pad || this.defaultPadding;
54538         var b = Ext.get(this.getEl()).getBox(),
54539             ce = Ext.get(constrainTo),
54540             s = ce.getScroll(),
54541             c,
54542             cd = ce.dom;
54543         if(cd == document.body){
54544             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
54545         }else{
54546             var xy = ce.getXY();
54547             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
54548         }
54549
54550
54551         var topSpace = b.y - c.y,
54552             leftSpace = b.x - c.x;
54553
54554         this.resetConstraints();
54555         this.setXConstraint(leftSpace - (pad.left||0), // left
54556                 c.width - leftSpace - b.width - (pad.right||0), //right
54557                                 this.xTickSize
54558         );
54559         this.setYConstraint(topSpace - (pad.top||0), //top
54560                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
54561                                 this.yTickSize
54562         );
54563     },
54564
54565     /**
54566      * Returns a reference to the linked element
54567      * @return {HTMLElement} the html element
54568      */
54569     getEl: function() {
54570         if (!this._domRef) {
54571             this._domRef = Ext.getDom(this.id);
54572         }
54573
54574         return this._domRef;
54575     },
54576
54577     /**
54578      * Returns a reference to the actual element to drag.  By default this is
54579      * the same as the html element, but it can be assigned to another
54580      * element. An example of this can be found in Ext.dd.DDProxy
54581      * @return {HTMLElement} the html element
54582      */
54583     getDragEl: function() {
54584         return Ext.getDom(this.dragElId);
54585     },
54586
54587     /**
54588      * Sets up the DragDrop object.  Must be called in the constructor of any
54589      * Ext.dd.DragDrop subclass
54590      * @param {String} id the id of the linked element
54591      * @param {String} sGroup the group of related items
54592      * @param {Object} config configuration attributes
54593      */
54594     init: function(id, sGroup, config) {
54595         this.initTarget(id, sGroup, config);
54596         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
54597         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
54598     },
54599
54600     /**
54601      * Initializes Targeting functionality only... the object does not
54602      * get a mousedown handler.
54603      * @param {String} id the id of the linked element
54604      * @param {String} sGroup the group of related items
54605      * @param {Object} config configuration attributes
54606      */
54607     initTarget: function(id, sGroup, config) {
54608         // configuration attributes
54609         this.config = config || {};
54610
54611         // create a local reference to the drag and drop manager
54612         this.DDMInstance = Ext.dd.DragDropManager;
54613         // initialize the groups array
54614         this.groups = {};
54615
54616         // assume that we have an element reference instead of an id if the
54617         // parameter is not a string
54618         if (typeof id !== "string") {
54619             id = Ext.id(id);
54620         }
54621
54622         // set the id
54623         this.id = id;
54624
54625         // add to an interaction group
54626         this.addToGroup((sGroup) ? sGroup : "default");
54627
54628         // We don't want to register this as the handle with the manager
54629         // so we just set the id rather than calling the setter.
54630         this.handleElId = id;
54631
54632         // the linked element is the element that gets dragged by default
54633         this.setDragElId(id);
54634
54635         // by default, clicked anchors will not start drag operations.
54636         this.invalidHandleTypes = { A: "A" };
54637         this.invalidHandleIds = {};
54638         this.invalidHandleClasses = [];
54639
54640         this.applyConfig();
54641
54642         this.handleOnAvailable();
54643     },
54644
54645     /**
54646      * Applies the configuration parameters that were passed into the constructor.
54647      * This is supposed to happen at each level through the inheritance chain.  So
54648      * a DDProxy implentation will execute apply config on DDProxy, DD, and
54649      * DragDrop in order to get all of the parameters that are available in
54650      * each object.
54651      */
54652     applyConfig: function() {
54653
54654         // configurable properties:
54655         //    padding, isTarget, maintainOffset, primaryButtonOnly
54656         this.padding           = this.config.padding || [0, 0, 0, 0];
54657         this.isTarget          = (this.config.isTarget !== false);
54658         this.maintainOffset    = (this.config.maintainOffset);
54659         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
54660
54661     },
54662
54663     /**
54664      * Executed when the linked element is available
54665      * @private
54666      */
54667     handleOnAvailable: function() {
54668         this.available = true;
54669         this.resetConstraints();
54670         this.onAvailable();
54671     },
54672
54673     /**
54674      * Configures the padding for the target zone in px.  Effectively expands
54675      * (or reduces) the virtual object size for targeting calculations.
54676      * Supports css-style shorthand; if only one parameter is passed, all sides
54677      * will have that padding, and if only two are passed, the top and bottom
54678      * will have the first param, the left and right the second.
54679      * @param {Number} iTop    Top pad
54680      * @param {Number} iRight  Right pad
54681      * @param {Number} iBot    Bot pad
54682      * @param {Number} iLeft   Left pad
54683      */
54684     setPadding: function(iTop, iRight, iBot, iLeft) {
54685         // this.padding = [iLeft, iRight, iTop, iBot];
54686         if (!iRight && 0 !== iRight) {
54687             this.padding = [iTop, iTop, iTop, iTop];
54688         } else if (!iBot && 0 !== iBot) {
54689             this.padding = [iTop, iRight, iTop, iRight];
54690         } else {
54691             this.padding = [iTop, iRight, iBot, iLeft];
54692         }
54693     },
54694
54695     /**
54696      * Stores the initial placement of the linked element.
54697      * @param {Number} diffX   the X offset, default 0
54698      * @param {Number} diffY   the Y offset, default 0
54699      */
54700     setInitPosition: function(diffX, diffY) {
54701         var el = this.getEl();
54702
54703         if (!this.DDMInstance.verifyEl(el)) {
54704             return;
54705         }
54706
54707         var dx = diffX || 0;
54708         var dy = diffY || 0;
54709
54710         var p = Ext.Element.getXY( el );
54711
54712         this.initPageX = p[0] - dx;
54713         this.initPageY = p[1] - dy;
54714
54715         this.lastPageX = p[0];
54716         this.lastPageY = p[1];
54717
54718         this.setStartPosition(p);
54719     },
54720
54721     /**
54722      * Sets the start position of the element.  This is set when the obj
54723      * is initialized, the reset when a drag is started.
54724      * @param pos current position (from previous lookup)
54725      * @private
54726      */
54727     setStartPosition: function(pos) {
54728         var p = pos || Ext.Element.getXY( this.getEl() );
54729         this.deltaSetXY = null;
54730
54731         this.startPageX = p[0];
54732         this.startPageY = p[1];
54733     },
54734
54735     /**
54736      * Adds this instance to a group of related drag/drop objects.  All
54737      * instances belong to at least one group, and can belong to as many
54738      * groups as needed.
54739      * @param {String} sGroup the name of the group
54740      */
54741     addToGroup: function(sGroup) {
54742         this.groups[sGroup] = true;
54743         this.DDMInstance.regDragDrop(this, sGroup);
54744     },
54745
54746     /**
54747      * Removes this instance from the supplied interaction group
54748      * @param {String} sGroup  The group to drop
54749      */
54750     removeFromGroup: function(sGroup) {
54751         if (this.groups[sGroup]) {
54752             delete this.groups[sGroup];
54753         }
54754
54755         this.DDMInstance.removeDDFromGroup(this, sGroup);
54756     },
54757
54758     /**
54759      * Allows you to specify that an element other than the linked element
54760      * will be moved with the cursor during a drag
54761      * @param {String} id the id of the element that will be used to initiate the drag
54762      */
54763     setDragElId: function(id) {
54764         this.dragElId = id;
54765     },
54766
54767     /**
54768      * Allows you to specify a child of the linked element that should be
54769      * used to initiate the drag operation.  An example of this would be if
54770      * you have a content div with text and links.  Clicking anywhere in the
54771      * content area would normally start the drag operation.  Use this method
54772      * to specify that an element inside of the content div is the element
54773      * that starts the drag operation.
54774      * @param {String} id the id of the element that will be used to
54775      * initiate the drag.
54776      */
54777     setHandleElId: function(id) {
54778         if (typeof id !== "string") {
54779             id = Ext.id(id);
54780         }
54781         this.handleElId = id;
54782         this.DDMInstance.regHandle(this.id, id);
54783     },
54784
54785     /**
54786      * Allows you to set an element outside of the linked element as a drag
54787      * handle
54788      * @param {String} id the id of the element that will be used to initiate the drag
54789      */
54790     setOuterHandleElId: function(id) {
54791         if (typeof id !== "string") {
54792             id = Ext.id(id);
54793         }
54794         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
54795         this.setHandleElId(id);
54796
54797         this.hasOuterHandles = true;
54798     },
54799
54800     /**
54801      * Removes all drag and drop hooks for this element
54802      */
54803     unreg: function() {
54804         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
54805         this._domRef = null;
54806         this.DDMInstance._remove(this);
54807     },
54808
54809     destroy : function(){
54810         this.unreg();
54811     },
54812
54813     /**
54814      * Returns true if this instance is locked, or the drag drop mgr is locked
54815      * (meaning that all drag/drop is disabled on the page.)
54816      * @return {Boolean} true if this obj or all drag/drop is locked, else
54817      * false
54818      */
54819     isLocked: function() {
54820         return (this.DDMInstance.isLocked() || this.locked);
54821     },
54822
54823     /**
54824      * Called when this object is clicked
54825      * @param {Event} e
54826      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
54827      * @private
54828      */
54829     handleMouseDown: function(e, oDD){
54830         if (this.primaryButtonOnly && e.button != 0) {
54831             return;
54832         }
54833
54834         if (this.isLocked()) {
54835             return;
54836         }
54837
54838         this.DDMInstance.refreshCache(this.groups);
54839
54840         var pt = e.getPoint();
54841         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
54842         } else {
54843             if (this.clickValidator(e)) {
54844                 // set the initial element position
54845                 this.setStartPosition();
54846                 this.b4MouseDown(e);
54847                 this.onMouseDown(e);
54848
54849                 this.DDMInstance.handleMouseDown(e, this);
54850
54851                 this.DDMInstance.stopEvent(e);
54852             } else {
54853
54854
54855             }
54856         }
54857     },
54858
54859     clickValidator: function(e) {
54860         var target = e.getTarget();
54861         return ( this.isValidHandleChild(target) &&
54862                     (this.id == this.handleElId ||
54863                         this.DDMInstance.handleWasClicked(target, this.id)) );
54864     },
54865
54866     /**
54867      * Allows you to specify a tag name that should not start a drag operation
54868      * when clicked.  This is designed to facilitate embedding links within a
54869      * drag handle that do something other than start the drag.
54870      * @method addInvalidHandleType
54871      * @param {String} tagName the type of element to exclude
54872      */
54873     addInvalidHandleType: function(tagName) {
54874         var type = tagName.toUpperCase();
54875         this.invalidHandleTypes[type] = type;
54876     },
54877
54878     /**
54879      * Lets you to specify an element id for a child of a drag handle
54880      * that should not initiate a drag
54881      * @method addInvalidHandleId
54882      * @param {String} id the element id of the element you wish to ignore
54883      */
54884     addInvalidHandleId: function(id) {
54885         if (typeof id !== "string") {
54886             id = Ext.id(id);
54887         }
54888         this.invalidHandleIds[id] = id;
54889     },
54890
54891     /**
54892      * Lets you specify a css class of elements that will not initiate a drag
54893      * @param {String} cssClass the class of the elements you wish to ignore
54894      */
54895     addInvalidHandleClass: function(cssClass) {
54896         this.invalidHandleClasses.push(cssClass);
54897     },
54898
54899     /**
54900      * Unsets an excluded tag name set by addInvalidHandleType
54901      * @param {String} tagName the type of element to unexclude
54902      */
54903     removeInvalidHandleType: function(tagName) {
54904         var type = tagName.toUpperCase();
54905         // this.invalidHandleTypes[type] = null;
54906         delete this.invalidHandleTypes[type];
54907     },
54908
54909     /**
54910      * Unsets an invalid handle id
54911      * @param {String} id the id of the element to re-enable
54912      */
54913     removeInvalidHandleId: function(id) {
54914         if (typeof id !== "string") {
54915             id = Ext.id(id);
54916         }
54917         delete this.invalidHandleIds[id];
54918     },
54919
54920     /**
54921      * Unsets an invalid css class
54922      * @param {String} cssClass the class of the element(s) you wish to
54923      * re-enable
54924      */
54925     removeInvalidHandleClass: function(cssClass) {
54926         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
54927             if (this.invalidHandleClasses[i] == cssClass) {
54928                 delete this.invalidHandleClasses[i];
54929             }
54930         }
54931     },
54932
54933     /**
54934      * Checks the tag exclusion list to see if this click should be ignored
54935      * @param {HTMLElement} node the HTMLElement to evaluate
54936      * @return {Boolean} true if this is a valid tag type, false if not
54937      */
54938     isValidHandleChild: function(node) {
54939
54940         var valid = true;
54941         // var n = (node.nodeName == "#text") ? node.parentNode : node;
54942         var nodeName;
54943         try {
54944             nodeName = node.nodeName.toUpperCase();
54945         } catch(e) {
54946             nodeName = node.nodeName;
54947         }
54948         valid = valid && !this.invalidHandleTypes[nodeName];
54949         valid = valid && !this.invalidHandleIds[node.id];
54950
54951         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
54952             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
54953         }
54954
54955
54956         return valid;
54957
54958     },
54959
54960     /**
54961      * Creates the array of horizontal tick marks if an interval was specified
54962      * in setXConstraint().
54963      * @private
54964      */
54965     setXTicks: function(iStartX, iTickSize) {
54966         this.xTicks = [];
54967         this.xTickSize = iTickSize;
54968
54969         var tickMap = {};
54970
54971         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
54972             if (!tickMap[i]) {
54973                 this.xTicks[this.xTicks.length] = i;
54974                 tickMap[i] = true;
54975             }
54976         }
54977
54978         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
54979             if (!tickMap[i]) {
54980                 this.xTicks[this.xTicks.length] = i;
54981                 tickMap[i] = true;
54982             }
54983         }
54984
54985         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
54986     },
54987
54988     /**
54989      * Creates the array of vertical tick marks if an interval was specified in
54990      * setYConstraint().
54991      * @private
54992      */
54993     setYTicks: function(iStartY, iTickSize) {
54994         this.yTicks = [];
54995         this.yTickSize = iTickSize;
54996
54997         var tickMap = {};
54998
54999         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
55000             if (!tickMap[i]) {
55001                 this.yTicks[this.yTicks.length] = i;
55002                 tickMap[i] = true;
55003             }
55004         }
55005
55006         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
55007             if (!tickMap[i]) {
55008                 this.yTicks[this.yTicks.length] = i;
55009                 tickMap[i] = true;
55010             }
55011         }
55012
55013         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
55014     },
55015
55016     /**
55017      * By default, the element can be dragged any place on the screen.  Use
55018      * this method to limit the horizontal travel of the element.  Pass in
55019      * 0,0 for the parameters if you want to lock the drag to the y axis.
55020      * @param {Number} iLeft the number of pixels the element can move to the left
55021      * @param {Number} iRight the number of pixels the element can move to the
55022      * right
55023      * @param {Number} iTickSize (optional) parameter for specifying that the
55024      * element should move iTickSize pixels at a time.
55025      */
55026     setXConstraint: function(iLeft, iRight, iTickSize) {
55027         this.leftConstraint = iLeft;
55028         this.rightConstraint = iRight;
55029
55030         this.minX = this.initPageX - iLeft;
55031         this.maxX = this.initPageX + iRight;
55032         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
55033
55034         this.constrainX = true;
55035     },
55036
55037     /**
55038      * Clears any constraints applied to this instance.  Also clears ticks
55039      * since they can't exist independent of a constraint at this time.
55040      */
55041     clearConstraints: function() {
55042         this.constrainX = false;
55043         this.constrainY = false;
55044         this.clearTicks();
55045     },
55046
55047     /**
55048      * Clears any tick interval defined for this instance
55049      */
55050     clearTicks: function() {
55051         this.xTicks = null;
55052         this.yTicks = null;
55053         this.xTickSize = 0;
55054         this.yTickSize = 0;
55055     },
55056
55057     /**
55058      * By default, the element can be dragged any place on the screen.  Set
55059      * this to limit the vertical travel of the element.  Pass in 0,0 for the
55060      * parameters if you want to lock the drag to the x axis.
55061      * @param {Number} iUp the number of pixels the element can move up
55062      * @param {Number} iDown the number of pixels the element can move down
55063      * @param {Number} iTickSize (optional) parameter for specifying that the
55064      * element should move iTickSize pixels at a time.
55065      */
55066     setYConstraint: function(iUp, iDown, iTickSize) {
55067         this.topConstraint = iUp;
55068         this.bottomConstraint = iDown;
55069
55070         this.minY = this.initPageY - iUp;
55071         this.maxY = this.initPageY + iDown;
55072         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
55073
55074         this.constrainY = true;
55075
55076     },
55077
55078     /**
55079      * Must be called if you manually reposition a dd element.
55080      * @param {Boolean} maintainOffset
55081      */
55082     resetConstraints: function() {
55083         // Maintain offsets if necessary
55084         if (this.initPageX || this.initPageX === 0) {
55085             // figure out how much this thing has moved
55086             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
55087             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
55088
55089             this.setInitPosition(dx, dy);
55090
55091         // This is the first time we have detected the element's position
55092         } else {
55093             this.setInitPosition();
55094         }
55095
55096         if (this.constrainX) {
55097             this.setXConstraint( this.leftConstraint,
55098                                  this.rightConstraint,
55099                                  this.xTickSize        );
55100         }
55101
55102         if (this.constrainY) {
55103             this.setYConstraint( this.topConstraint,
55104                                  this.bottomConstraint,
55105                                  this.yTickSize         );
55106         }
55107     },
55108
55109     /**
55110      * Normally the drag element is moved pixel by pixel, but we can specify
55111      * that it move a number of pixels at a time.  This method resolves the
55112      * location when we have it set up like this.
55113      * @param {Number} val where we want to place the object
55114      * @param {Number[]} tickArray sorted array of valid points
55115      * @return {Number} the closest tick
55116      * @private
55117      */
55118     getTick: function(val, tickArray) {
55119         if (!tickArray) {
55120             // If tick interval is not defined, it is effectively 1 pixel,
55121             // so we return the value passed to us.
55122             return val;
55123         } else if (tickArray[0] >= val) {
55124             // The value is lower than the first tick, so we return the first
55125             // tick.
55126             return tickArray[0];
55127         } else {
55128             for (var i=0, len=tickArray.length; i<len; ++i) {
55129                 var next = i + 1;
55130                 if (tickArray[next] && tickArray[next] >= val) {
55131                     var diff1 = val - tickArray[i];
55132                     var diff2 = tickArray[next] - val;
55133                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
55134                 }
55135             }
55136
55137             // The value is larger than the last tick, so we return the last
55138             // tick.
55139             return tickArray[tickArray.length - 1];
55140         }
55141     },
55142
55143     /**
55144      * toString method
55145      * @return {String} string representation of the dd obj
55146      */
55147     toString: function() {
55148         return ("DragDrop " + this.id);
55149     }
55150
55151 });
55152
55153 /*
55154  * This is a derivative of the similarly named class in the YUI Library.
55155  * The original license:
55156  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55157  * Code licensed under the BSD License:
55158  * http://developer.yahoo.net/yui/license.txt
55159  */
55160
55161
55162 /**
55163  * @class Ext.dd.DD
55164  * A DragDrop implementation where the linked element follows the
55165  * mouse cursor during a drag.
55166  * @extends Ext.dd.DragDrop
55167  */
55168 Ext.define('Ext.dd.DD', {
55169     extend: 'Ext.dd.DragDrop',
55170     requires: ['Ext.dd.DragDropManager'],
55171
55172     /**
55173      * Creates new DD instance.
55174      * @param {String} id the id of the linked element
55175      * @param {String} sGroup the group of related DragDrop items
55176      * @param {Object} config an object containing configurable attributes.
55177      * Valid properties for DD: scroll
55178      */
55179     constructor: function(id, sGroup, config) {
55180         if (id) {
55181             this.init(id, sGroup, config);
55182         }
55183     },
55184
55185     /**
55186      * When set to true, the utility automatically tries to scroll the browser
55187      * window when a drag and drop element is dragged near the viewport boundary.
55188      * Defaults to true.
55189      * @property scroll
55190      * @type Boolean
55191      */
55192     scroll: true,
55193
55194     /**
55195      * Sets the pointer offset to the distance between the linked element's top
55196      * left corner and the location the element was clicked
55197      * @method autoOffset
55198      * @param {Number} iPageX the X coordinate of the click
55199      * @param {Number} iPageY the Y coordinate of the click
55200      */
55201     autoOffset: function(iPageX, iPageY) {
55202         var x = iPageX - this.startPageX;
55203         var y = iPageY - this.startPageY;
55204         this.setDelta(x, y);
55205     },
55206
55207     /**
55208      * Sets the pointer offset.  You can call this directly to force the
55209      * offset to be in a particular location (e.g., pass in 0,0 to set it
55210      * to the center of the object)
55211      * @method setDelta
55212      * @param {Number} iDeltaX the distance from the left
55213      * @param {Number} iDeltaY the distance from the top
55214      */
55215     setDelta: function(iDeltaX, iDeltaY) {
55216         this.deltaX = iDeltaX;
55217         this.deltaY = iDeltaY;
55218     },
55219
55220     /**
55221      * Sets the drag element to the location of the mousedown or click event,
55222      * maintaining the cursor location relative to the location on the element
55223      * that was clicked.  Override this if you want to place the element in a
55224      * location other than where the cursor is.
55225      * @method setDragElPos
55226      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55227      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55228      */
55229     setDragElPos: function(iPageX, iPageY) {
55230         // the first time we do this, we are going to check to make sure
55231         // the element has css positioning
55232
55233         var el = this.getDragEl();
55234         this.alignElWithMouse(el, iPageX, iPageY);
55235     },
55236
55237     /**
55238      * Sets the element to the location of the mousedown or click event,
55239      * maintaining the cursor location relative to the location on the element
55240      * that was clicked.  Override this if you want to place the element in a
55241      * location other than where the cursor is.
55242      * @method alignElWithMouse
55243      * @param {HTMLElement} el the element to move
55244      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55245      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55246      */
55247     alignElWithMouse: function(el, iPageX, iPageY) {
55248         var oCoord = this.getTargetCoord(iPageX, iPageY),
55249             fly = el.dom ? el : Ext.fly(el, '_dd'),
55250             elSize = fly.getSize(),
55251             EL = Ext.Element,
55252             vpSize;
55253
55254         if (!this.deltaSetXY) {
55255             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
55256             var aCoord = [
55257                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
55258                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
55259             ];
55260             fly.setXY(aCoord);
55261             var newLeft = fly.getLeft(true);
55262             var newTop  = fly.getTop(true);
55263             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
55264         } else {
55265             vpSize = this.cachedViewportSize;
55266             fly.setLeftTop(
55267                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
55268                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
55269             );
55270         }
55271
55272         this.cachePosition(oCoord.x, oCoord.y);
55273         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
55274         return oCoord;
55275     },
55276
55277     /**
55278      * Saves the most recent position so that we can reset the constraints and
55279      * tick marks on-demand.  We need to know this so that we can calculate the
55280      * number of pixels the element is offset from its original position.
55281      * @method cachePosition
55282      * @param {Number} iPageX (optional) the current x position (this just makes it so we
55283      * don't have to look it up again)
55284      * @param {Number} iPageY (optional) the current y position (this just makes it so we
55285      * don't have to look it up again)
55286      */
55287     cachePosition: function(iPageX, iPageY) {
55288         if (iPageX) {
55289             this.lastPageX = iPageX;
55290             this.lastPageY = iPageY;
55291         } else {
55292             var aCoord = Ext.Element.getXY(this.getEl());
55293             this.lastPageX = aCoord[0];
55294             this.lastPageY = aCoord[1];
55295         }
55296     },
55297
55298     /**
55299      * Auto-scroll the window if the dragged object has been moved beyond the
55300      * visible window boundary.
55301      * @method autoScroll
55302      * @param {Number} x the drag element's x position
55303      * @param {Number} y the drag element's y position
55304      * @param {Number} h the height of the drag element
55305      * @param {Number} w the width of the drag element
55306      * @private
55307      */
55308     autoScroll: function(x, y, h, w) {
55309
55310         if (this.scroll) {
55311             // The client height
55312             var clientH = Ext.Element.getViewHeight();
55313
55314             // The client width
55315             var clientW = Ext.Element.getViewWidth();
55316
55317             // The amt scrolled down
55318             var st = this.DDMInstance.getScrollTop();
55319
55320             // The amt scrolled right
55321             var sl = this.DDMInstance.getScrollLeft();
55322
55323             // Location of the bottom of the element
55324             var bot = h + y;
55325
55326             // Location of the right of the element
55327             var right = w + x;
55328
55329             // The distance from the cursor to the bottom of the visible area,
55330             // adjusted so that we don't scroll if the cursor is beyond the
55331             // element drag constraints
55332             var toBot = (clientH + st - y - this.deltaY);
55333
55334             // The distance from the cursor to the right of the visible area
55335             var toRight = (clientW + sl - x - this.deltaX);
55336
55337
55338             // How close to the edge the cursor must be before we scroll
55339             // var thresh = (document.all) ? 100 : 40;
55340             var thresh = 40;
55341
55342             // How many pixels to scroll per autoscroll op.  This helps to reduce
55343             // clunky scrolling. IE is more sensitive about this ... it needs this
55344             // value to be higher.
55345             var scrAmt = (document.all) ? 80 : 30;
55346
55347             // Scroll down if we are near the bottom of the visible page and the
55348             // obj extends below the crease
55349             if ( bot > clientH && toBot < thresh ) {
55350                 window.scrollTo(sl, st + scrAmt);
55351             }
55352
55353             // Scroll up if the window is scrolled down and the top of the object
55354             // goes above the top border
55355             if ( y < st && st > 0 && y - st < thresh ) {
55356                 window.scrollTo(sl, st - scrAmt);
55357             }
55358
55359             // Scroll right if the obj is beyond the right border and the cursor is
55360             // near the border.
55361             if ( right > clientW && toRight < thresh ) {
55362                 window.scrollTo(sl + scrAmt, st);
55363             }
55364
55365             // Scroll left if the window has been scrolled to the right and the obj
55366             // extends past the left border
55367             if ( x < sl && sl > 0 && x - sl < thresh ) {
55368                 window.scrollTo(sl - scrAmt, st);
55369             }
55370         }
55371     },
55372
55373     /**
55374      * Finds the location the element should be placed if we want to move
55375      * it to where the mouse location less the click offset would place us.
55376      * @method getTargetCoord
55377      * @param {Number} iPageX the X coordinate of the click
55378      * @param {Number} iPageY the Y coordinate of the click
55379      * @return an object that contains the coordinates (Object.x and Object.y)
55380      * @private
55381      */
55382     getTargetCoord: function(iPageX, iPageY) {
55383         var x = iPageX - this.deltaX;
55384         var y = iPageY - this.deltaY;
55385
55386         if (this.constrainX) {
55387             if (x < this.minX) {
55388                 x = this.minX;
55389             }
55390             if (x > this.maxX) {
55391                 x = this.maxX;
55392             }
55393         }
55394
55395         if (this.constrainY) {
55396             if (y < this.minY) {
55397                 y = this.minY;
55398             }
55399             if (y > this.maxY) {
55400                 y = this.maxY;
55401             }
55402         }
55403
55404         x = this.getTick(x, this.xTicks);
55405         y = this.getTick(y, this.yTicks);
55406
55407
55408         return {x: x, y: y};
55409     },
55410
55411     /**
55412      * Sets up config options specific to this class. Overrides
55413      * Ext.dd.DragDrop, but all versions of this method through the
55414      * inheritance chain are called
55415      */
55416     applyConfig: function() {
55417         this.callParent();
55418         this.scroll = (this.config.scroll !== false);
55419     },
55420
55421     /**
55422      * Event that fires prior to the onMouseDown event.  Overrides
55423      * Ext.dd.DragDrop.
55424      */
55425     b4MouseDown: function(e) {
55426         // this.resetConstraints();
55427         this.autoOffset(e.getPageX(), e.getPageY());
55428     },
55429
55430     /**
55431      * Event that fires prior to the onDrag event.  Overrides
55432      * Ext.dd.DragDrop.
55433      */
55434     b4Drag: function(e) {
55435         this.setDragElPos(e.getPageX(), e.getPageY());
55436     },
55437
55438     toString: function() {
55439         return ("DD " + this.id);
55440     }
55441
55442     //////////////////////////////////////////////////////////////////////////
55443     // Debugging ygDragDrop events that can be overridden
55444     //////////////////////////////////////////////////////////////////////////
55445     /*
55446     startDrag: function(x, y) {
55447     },
55448
55449     onDrag: function(e) {
55450     },
55451
55452     onDragEnter: function(e, id) {
55453     },
55454
55455     onDragOver: function(e, id) {
55456     },
55457
55458     onDragOut: function(e, id) {
55459     },
55460
55461     onDragDrop: function(e, id) {
55462     },
55463
55464     endDrag: function(e) {
55465     }
55466
55467     */
55468
55469 });
55470
55471 /*
55472  * This is a derivative of the similarly named class in the YUI Library.
55473  * The original license:
55474  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55475  * Code licensed under the BSD License:
55476  * http://developer.yahoo.net/yui/license.txt
55477  */
55478
55479 /**
55480  * @class Ext.dd.DDProxy
55481  * @extends Ext.dd.DD
55482  * A DragDrop implementation that inserts an empty, bordered div into
55483  * the document that follows the cursor during drag operations.  At the time of
55484  * the click, the frame div is resized to the dimensions of the linked html
55485  * element, and moved to the exact location of the linked element.
55486  *
55487  * References to the "frame" element refer to the single proxy element that
55488  * was created to be dragged in place of all DDProxy elements on the
55489  * page.
55490  */
55491 Ext.define('Ext.dd.DDProxy', {
55492     extend: 'Ext.dd.DD',
55493
55494     statics: {
55495         /**
55496          * The default drag frame div id
55497          * @static
55498          */
55499         dragElId: "ygddfdiv"
55500     },
55501
55502     /**
55503      * Creates new DDProxy.
55504      * @param {String} id the id of the linked html element
55505      * @param {String} sGroup the group of related DragDrop objects
55506      * @param {Object} config an object containing configurable attributes.
55507      * Valid properties for DDProxy in addition to those in DragDrop:
55508      * 
55509      * - resizeFrame
55510      * - centerFrame
55511      * - dragElId
55512      */
55513     constructor: function(id, sGroup, config) {
55514         if (id) {
55515             this.init(id, sGroup, config);
55516             this.initFrame();
55517         }
55518     },
55519
55520     /**
55521      * By default we resize the drag frame to be the same size as the element
55522      * we want to drag (this is to get the frame effect).  We can turn it off
55523      * if we want a different behavior.
55524      * @property resizeFrame
55525      * @type Boolean
55526      */
55527     resizeFrame: true,
55528
55529     /**
55530      * By default the frame is positioned exactly where the drag element is, so
55531      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
55532      * you do not have constraints on the obj is to have the drag frame centered
55533      * around the cursor.  Set centerFrame to true for this effect.
55534      * @property centerFrame
55535      * @type Boolean
55536      */
55537     centerFrame: false,
55538
55539     /**
55540      * Creates the proxy element if it does not yet exist
55541      * @method createFrame
55542      */
55543     createFrame: function() {
55544         var self = this;
55545         var body = document.body;
55546
55547         if (!body || !body.firstChild) {
55548             setTimeout( function() { self.createFrame(); }, 50 );
55549             return;
55550         }
55551
55552         var div = this.getDragEl();
55553
55554         if (!div) {
55555             div    = document.createElement("div");
55556             div.id = this.dragElId;
55557             var s  = div.style;
55558
55559             s.position   = "absolute";
55560             s.visibility = "hidden";
55561             s.cursor     = "move";
55562             s.border     = "2px solid #aaa";
55563             s.zIndex     = 999;
55564
55565             // appendChild can blow up IE if invoked prior to the window load event
55566             // while rendering a table.  It is possible there are other scenarios
55567             // that would cause this to happen as well.
55568             body.insertBefore(div, body.firstChild);
55569         }
55570     },
55571
55572     /**
55573      * Initialization for the drag frame element.  Must be called in the
55574      * constructor of all subclasses
55575      * @method initFrame
55576      */
55577     initFrame: function() {
55578         this.createFrame();
55579     },
55580
55581     applyConfig: function() {
55582         this.callParent();
55583
55584         this.resizeFrame = (this.config.resizeFrame !== false);
55585         this.centerFrame = (this.config.centerFrame);
55586         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
55587     },
55588
55589     /**
55590      * Resizes the drag frame to the dimensions of the clicked object, positions
55591      * it over the object, and finally displays it
55592      * @method showFrame
55593      * @param {Number} iPageX X click position
55594      * @param {Number} iPageY Y click position
55595      * @private
55596      */
55597     showFrame: function(iPageX, iPageY) {
55598         var el = this.getEl();
55599         var dragEl = this.getDragEl();
55600         var s = dragEl.style;
55601
55602         this._resizeProxy();
55603
55604         if (this.centerFrame) {
55605             this.setDelta( Math.round(parseInt(s.width,  10)/2),
55606                            Math.round(parseInt(s.height, 10)/2) );
55607         }
55608
55609         this.setDragElPos(iPageX, iPageY);
55610
55611         Ext.fly(dragEl).show();
55612     },
55613
55614     /**
55615      * The proxy is automatically resized to the dimensions of the linked
55616      * element when a drag is initiated, unless resizeFrame is set to false
55617      * @method _resizeProxy
55618      * @private
55619      */
55620     _resizeProxy: function() {
55621         if (this.resizeFrame) {
55622             var el = this.getEl();
55623             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
55624         }
55625     },
55626
55627     // overrides Ext.dd.DragDrop
55628     b4MouseDown: function(e) {
55629         var x = e.getPageX();
55630         var y = e.getPageY();
55631         this.autoOffset(x, y);
55632         this.setDragElPos(x, y);
55633     },
55634
55635     // overrides Ext.dd.DragDrop
55636     b4StartDrag: function(x, y) {
55637         // show the drag frame
55638         this.showFrame(x, y);
55639     },
55640
55641     // overrides Ext.dd.DragDrop
55642     b4EndDrag: function(e) {
55643         Ext.fly(this.getDragEl()).hide();
55644     },
55645
55646     // overrides Ext.dd.DragDrop
55647     // By default we try to move the element to the last location of the frame.
55648     // This is so that the default behavior mirrors that of Ext.dd.DD.
55649     endDrag: function(e) {
55650
55651         var lel = this.getEl();
55652         var del = this.getDragEl();
55653
55654         // Show the drag frame briefly so we can get its position
55655         del.style.visibility = "";
55656
55657         this.beforeMove();
55658         // Hide the linked element before the move to get around a Safari
55659         // rendering bug.
55660         lel.style.visibility = "hidden";
55661         Ext.dd.DDM.moveToEl(lel, del);
55662         del.style.visibility = "hidden";
55663         lel.style.visibility = "";
55664
55665         this.afterDrag();
55666     },
55667
55668     beforeMove : function(){
55669
55670     },
55671
55672     afterDrag : function(){
55673
55674     },
55675
55676     toString: function() {
55677         return ("DDProxy " + this.id);
55678     }
55679
55680 });
55681
55682 /**
55683  * @class Ext.dd.DragSource
55684  * @extends Ext.dd.DDProxy
55685  * A simple class that provides the basic implementation needed to make any element draggable.
55686  */
55687 Ext.define('Ext.dd.DragSource', {
55688     extend: 'Ext.dd.DDProxy',
55689     requires: [
55690         'Ext.dd.StatusProxy',
55691         'Ext.dd.DragDropManager'
55692     ],
55693
55694     /**
55695      * @cfg {String} ddGroup
55696      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
55697      * interact with other drag drop objects in the same group.
55698      */
55699
55700     /**
55701      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
55702      * The CSS class returned to the drag source when drop is allowed.
55703      */
55704     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
55705     /**
55706      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
55707      * The CSS class returned to the drag source when drop is not allowed.
55708      */
55709     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
55710
55711     /**
55712      * @cfg {Boolean} animRepair
55713      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
55714      */
55715     animRepair: true,
55716
55717     /**
55718      * @cfg {String} repairHighlightColor
55719      * The color to use when visually highlighting the drag source in the afterRepair
55720      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
55721      * a preceding '#'.
55722      */
55723     repairHighlightColor: 'c3daf9',
55724
55725     /**
55726      * Creates new drag-source.
55727      * @constructor
55728      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
55729      * @param {Object} config (optional) Config object.
55730      */
55731     constructor: function(el, config) {
55732         this.el = Ext.get(el);
55733         if(!this.dragData){
55734             this.dragData = {};
55735         }
55736
55737         Ext.apply(this, config);
55738
55739         if(!this.proxy){
55740             this.proxy = Ext.create('Ext.dd.StatusProxy', {
55741                 animRepair: this.animRepair
55742             });
55743         }
55744         this.callParent([this.el.dom, this.ddGroup || this.group,
55745               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
55746
55747         this.dragging = false;
55748     },
55749
55750     /**
55751      * Returns the data object associated with this drag source
55752      * @return {Object} data An object containing arbitrary data
55753      */
55754     getDragData : function(e){
55755         return this.dragData;
55756     },
55757
55758     // private
55759     onDragEnter : function(e, id){
55760         var target = Ext.dd.DragDropManager.getDDById(id);
55761         this.cachedTarget = target;
55762         if (this.beforeDragEnter(target, e, id) !== false) {
55763             if (target.isNotifyTarget) {
55764                 var status = target.notifyEnter(this, e, this.dragData);
55765                 this.proxy.setStatus(status);
55766             } else {
55767                 this.proxy.setStatus(this.dropAllowed);
55768             }
55769
55770             if (this.afterDragEnter) {
55771                 /**
55772                  * An empty function by default, but provided so that you can perform a custom action
55773                  * when the dragged item enters the drop target by providing an implementation.
55774                  * @param {Ext.dd.DragDrop} target The drop target
55775                  * @param {Event} e The event object
55776                  * @param {String} id The id of the dragged element
55777                  * @method afterDragEnter
55778                  */
55779                 this.afterDragEnter(target, e, id);
55780             }
55781         }
55782     },
55783
55784     /**
55785      * An empty function by default, but provided so that you can perform a custom action
55786      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
55787      * @param {Ext.dd.DragDrop} target The drop target
55788      * @param {Event} e The event object
55789      * @param {String} id The id of the dragged element
55790      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55791      */
55792     beforeDragEnter: function(target, e, id) {
55793         return true;
55794     },
55795
55796     // private
55797     alignElWithMouse: function() {
55798         this.callParent(arguments);
55799         this.proxy.sync();
55800     },
55801
55802     // private
55803     onDragOver: function(e, id) {
55804         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55805         if (this.beforeDragOver(target, e, id) !== false) {
55806             if(target.isNotifyTarget){
55807                 var status = target.notifyOver(this, e, this.dragData);
55808                 this.proxy.setStatus(status);
55809             }
55810
55811             if (this.afterDragOver) {
55812                 /**
55813                  * An empty function by default, but provided so that you can perform a custom action
55814                  * while the dragged item is over the drop target by providing an implementation.
55815                  * @param {Ext.dd.DragDrop} target The drop target
55816                  * @param {Event} e The event object
55817                  * @param {String} id The id of the dragged element
55818                  * @method afterDragOver
55819                  */
55820                 this.afterDragOver(target, e, id);
55821             }
55822         }
55823     },
55824
55825     /**
55826      * An empty function by default, but provided so that you can perform a custom action
55827      * while the dragged item is over the drop target and optionally cancel the onDragOver.
55828      * @param {Ext.dd.DragDrop} target The drop target
55829      * @param {Event} e The event object
55830      * @param {String} id The id of the dragged element
55831      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55832      */
55833     beforeDragOver: function(target, e, id) {
55834         return true;
55835     },
55836
55837     // private
55838     onDragOut: function(e, id) {
55839         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55840         if (this.beforeDragOut(target, e, id) !== false) {
55841             if (target.isNotifyTarget) {
55842                 target.notifyOut(this, e, this.dragData);
55843             }
55844             this.proxy.reset();
55845             if (this.afterDragOut) {
55846                 /**
55847                  * An empty function by default, but provided so that you can perform a custom action
55848                  * after the dragged item is dragged out of the target without dropping.
55849                  * @param {Ext.dd.DragDrop} target The drop target
55850                  * @param {Event} e The event object
55851                  * @param {String} id The id of the dragged element
55852                  * @method afterDragOut
55853                  */
55854                 this.afterDragOut(target, e, id);
55855             }
55856         }
55857         this.cachedTarget = null;
55858     },
55859
55860     /**
55861      * An empty function by default, but provided so that you can perform a custom action before the dragged
55862      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
55863      * @param {Ext.dd.DragDrop} target The drop target
55864      * @param {Event} e The event object
55865      * @param {String} id The id of the dragged element
55866      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55867      */
55868     beforeDragOut: function(target, e, id){
55869         return true;
55870     },
55871
55872     // private
55873     onDragDrop: function(e, id){
55874         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55875         if (this.beforeDragDrop(target, e, id) !== false) {
55876             if (target.isNotifyTarget) {
55877                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
55878                     this.onValidDrop(target, e, id);
55879                 } else {
55880                     this.onInvalidDrop(target, e, id);
55881                 }
55882             } else {
55883                 this.onValidDrop(target, e, id);
55884             }
55885
55886             if (this.afterDragDrop) {
55887                 /**
55888                  * An empty function by default, but provided so that you can perform a custom action
55889                  * after a valid drag drop has occurred by providing an implementation.
55890                  * @param {Ext.dd.DragDrop} target The drop target
55891                  * @param {Event} e The event object
55892                  * @param {String} id The id of the dropped element
55893                  * @method afterDragDrop
55894                  */
55895                 this.afterDragDrop(target, e, id);
55896             }
55897         }
55898         delete this.cachedTarget;
55899     },
55900
55901     /**
55902      * An empty function by default, but provided so that you can perform a custom action before the dragged
55903      * item is dropped onto the target and optionally cancel the onDragDrop.
55904      * @param {Ext.dd.DragDrop} target The drop target
55905      * @param {Event} e The event object
55906      * @param {String} id The id of the dragged element
55907      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
55908      */
55909     beforeDragDrop: function(target, e, id){
55910         return true;
55911     },
55912
55913     // private
55914     onValidDrop: function(target, e, id){
55915         this.hideProxy();
55916         if(this.afterValidDrop){
55917             /**
55918              * An empty function by default, but provided so that you can perform a custom action
55919              * after a valid drop has occurred by providing an implementation.
55920              * @param {Object} target The target DD
55921              * @param {Event} e The event object
55922              * @param {String} id The id of the dropped element
55923              * @method afterValidDrop
55924              */
55925             this.afterValidDrop(target, e, id);
55926         }
55927     },
55928
55929     // private
55930     getRepairXY: function(e, data){
55931         return this.el.getXY();
55932     },
55933
55934     // private
55935     onInvalidDrop: function(target, e, id) {
55936         this.beforeInvalidDrop(target, e, id);
55937         if (this.cachedTarget) {
55938             if(this.cachedTarget.isNotifyTarget){
55939                 this.cachedTarget.notifyOut(this, e, this.dragData);
55940             }
55941             this.cacheTarget = null;
55942         }
55943         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
55944
55945         if (this.afterInvalidDrop) {
55946             /**
55947              * An empty function by default, but provided so that you can perform a custom action
55948              * after an invalid drop has occurred by providing an implementation.
55949              * @param {Event} e The event object
55950              * @param {String} id The id of the dropped element
55951              * @method afterInvalidDrop
55952              */
55953             this.afterInvalidDrop(e, id);
55954         }
55955     },
55956
55957     // private
55958     afterRepair: function() {
55959         var me = this;
55960         if (Ext.enableFx) {
55961             me.el.highlight(me.repairHighlightColor);
55962         }
55963         me.dragging = false;
55964     },
55965
55966     /**
55967      * An empty function by default, but provided so that you can perform a custom action after an invalid
55968      * drop has occurred.
55969      * @param {Ext.dd.DragDrop} target The drop target
55970      * @param {Event} e The event object
55971      * @param {String} id The id of the dragged element
55972      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
55973      */
55974     beforeInvalidDrop: function(target, e, id) {
55975         return true;
55976     },
55977
55978     // private
55979     handleMouseDown: function(e) {
55980         if (this.dragging) {
55981             return;
55982         }
55983         var data = this.getDragData(e);
55984         if (data && this.onBeforeDrag(data, e) !== false) {
55985             this.dragData = data;
55986             this.proxy.stop();
55987             this.callParent(arguments);
55988         }
55989     },
55990
55991     /**
55992      * An empty function by default, but provided so that you can perform a custom action before the initial
55993      * drag event begins and optionally cancel it.
55994      * @param {Object} data An object containing arbitrary data to be shared with drop targets
55995      * @param {Event} e The event object
55996      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55997      */
55998     onBeforeDrag: function(data, e){
55999         return true;
56000     },
56001
56002     /**
56003      * An empty function by default, but provided so that you can perform a custom action once the initial
56004      * drag event has begun.  The drag cannot be canceled from this function.
56005      * @param {Number} x The x position of the click on the dragged object
56006      * @param {Number} y The y position of the click on the dragged object
56007      * @method
56008      */
56009     onStartDrag: Ext.emptyFn,
56010
56011     // private override
56012     startDrag: function(x, y) {
56013         this.proxy.reset();
56014         this.dragging = true;
56015         this.proxy.update("");
56016         this.onInitDrag(x, y);
56017         this.proxy.show();
56018     },
56019
56020     // private
56021     onInitDrag: function(x, y) {
56022         var clone = this.el.dom.cloneNode(true);
56023         clone.id = Ext.id(); // prevent duplicate ids
56024         this.proxy.update(clone);
56025         this.onStartDrag(x, y);
56026         return true;
56027     },
56028
56029     /**
56030      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
56031      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
56032      */
56033     getProxy: function() {
56034         return this.proxy;
56035     },
56036
56037     /**
56038      * Hides the drag source's {@link Ext.dd.StatusProxy}
56039      */
56040     hideProxy: function() {
56041         this.proxy.hide();
56042         this.proxy.reset(true);
56043         this.dragging = false;
56044     },
56045
56046     // private
56047     triggerCacheRefresh: function() {
56048         Ext.dd.DDM.refreshCache(this.groups);
56049     },
56050
56051     // private - override to prevent hiding
56052     b4EndDrag: function(e) {
56053     },
56054
56055     // private - override to prevent moving
56056     endDrag : function(e){
56057         this.onEndDrag(this.dragData, e);
56058     },
56059
56060     // private
56061     onEndDrag : function(data, e){
56062     },
56063
56064     // private - pin to cursor
56065     autoOffset : function(x, y) {
56066         this.setDelta(-12, -20);
56067     },
56068
56069     destroy: function(){
56070         this.callParent();
56071         Ext.destroy(this.proxy);
56072     }
56073 });
56074
56075 // private - DD implementation for Panels
56076 Ext.define('Ext.panel.DD', {
56077     extend: 'Ext.dd.DragSource',
56078     requires: ['Ext.panel.Proxy'],
56079
56080     constructor : function(panel, cfg){
56081         this.panel = panel;
56082         this.dragData = {panel: panel};
56083         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
56084
56085         this.callParent([panel.el, cfg]);
56086
56087         Ext.defer(function() {
56088             var header = panel.header,
56089                 el = panel.body;
56090
56091             if(header){
56092                 this.setHandleElId(header.id);
56093                 el = header.el;
56094             }
56095             el.setStyle('cursor', 'move');
56096             this.scroll = false;
56097         }, 200, this);
56098     },
56099
56100     showFrame: Ext.emptyFn,
56101     startDrag: Ext.emptyFn,
56102     b4StartDrag: function(x, y) {
56103         this.proxy.show();
56104     },
56105     b4MouseDown: function(e) {
56106         var x = e.getPageX(),
56107             y = e.getPageY();
56108         this.autoOffset(x, y);
56109     },
56110     onInitDrag : function(x, y){
56111         this.onStartDrag(x, y);
56112         return true;
56113     },
56114     createFrame : Ext.emptyFn,
56115     getDragEl : function(e){
56116         return this.proxy.ghost.el.dom;
56117     },
56118     endDrag : function(e){
56119         this.proxy.hide();
56120         this.panel.saveState();
56121     },
56122
56123     autoOffset : function(x, y) {
56124         x -= this.startPageX;
56125         y -= this.startPageY;
56126         this.setDelta(x, y);
56127     }
56128 });
56129
56130 /**
56131  * @class Ext.layout.component.Dock
56132  * @extends Ext.layout.component.AbstractDock
56133  * @private
56134  */
56135 Ext.define('Ext.layout.component.Dock', {
56136
56137     /* Begin Definitions */
56138
56139     alias: ['layout.dock'],
56140
56141     extend: 'Ext.layout.component.AbstractDock'
56142
56143     /* End Definitions */
56144
56145 });
56146 /**
56147  * Panel is a container that has specific functionality and structural components that make it the perfect building
56148  * block for application-oriented user interfaces.
56149  *
56150  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
56151  * {@link Ext.container.Container#layout layout}, and containing child Components.
56152  *
56153  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
56154  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
56155  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
56156  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
56157  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
56158  *
56159  * {@img Ext.panel.Panel/panel.png Panel components}
56160  *
56161  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
56162  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
56163  *
56164  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
56165  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
56166  * is {@link Ext.container.Container#add completely managed by the framework}.
56167  *
56168  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
56169  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
56170  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
56171  * `{@link #closeAction closeAction}: 'hide'`.
56172  *
56173  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
56174  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
56175  * Panel into the document, here's how to do it:
56176  *
56177  *     @example
56178  *     Ext.create('Ext.panel.Panel', {
56179  *         title: 'Hello',
56180  *         width: 200,
56181  *         html: '<p>World!</p>',
56182  *         renderTo: Ext.getBody()
56183  *     });
56184  *
56185  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
56186  * constituent part of a Container:
56187  *
56188  *     @example
56189  *     var filterPanel = Ext.create('Ext.panel.Panel', {
56190  *         bodyPadding: 5,  // Don't want content to crunch against the borders
56191  *         width: 300,
56192  *         title: 'Filters',
56193  *         items: [{
56194  *             xtype: 'datefield',
56195  *             fieldLabel: 'Start date'
56196  *         }, {
56197  *             xtype: 'datefield',
56198  *             fieldLabel: 'End date'
56199  *         }],
56200  *         renderTo: Ext.getBody()
56201  *     });
56202  *
56203  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
56204  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
56205  * size and position its child Components.
56206  *
56207  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
56208  * arranging child Components:
56209  *
56210  *     @example
56211  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
56212  *         title: 'Results',
56213  *         width: 600,
56214  *         height: 400,
56215  *         renderTo: Ext.getBody(),
56216  *         layout: {
56217  *             type: 'vbox',       // Arrange child items vertically
56218  *             align: 'stretch',    // Each takes up full width
56219  *             padding: 5
56220  *         },
56221  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
56222  *             xtype: 'grid',
56223  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
56224  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
56225  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
56226  *         }, {
56227  *             xtype: 'splitter'   // A splitter between the two child items
56228  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
56229  *             title: 'Details',
56230  *             bodyPadding: 5,
56231  *             items: [{
56232  *                 fieldLabel: 'Data item',
56233  *                 xtype: 'textfield'
56234  *             }], // An array of form fields
56235  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
56236  *         }]
56237  *     });
56238  *
56239  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
56240  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
56241  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
56242  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
56243  * distribute available space proportionately.
56244  *
56245  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
56246  * within its content area.
56247  *
56248  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
56249  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
56250  * browser size.
56251  */
56252 Ext.define('Ext.panel.Panel', {
56253     extend: 'Ext.panel.AbstractPanel',
56254     requires: [
56255         'Ext.panel.Header',
56256         'Ext.fx.Anim',
56257         'Ext.util.KeyMap',
56258         'Ext.panel.DD',
56259         'Ext.XTemplate',
56260         'Ext.layout.component.Dock',
56261         'Ext.util.Memento'
56262     ],
56263     alias: 'widget.panel',
56264     alternateClassName: 'Ext.Panel',
56265
56266     /**
56267      * @cfg {String} collapsedCls
56268      * A CSS class to add to the panel's element after it has been collapsed.
56269      */
56270     collapsedCls: 'collapsed',
56271
56272     /**
56273      * @cfg {Boolean} animCollapse
56274      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
56275      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
56276      * duration in milliseconds.
56277      */
56278     animCollapse: Ext.enableFx,
56279
56280     /**
56281      * @cfg {Number} minButtonWidth
56282      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
56283      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
56284      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
56285      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
56286      * their parent container.
56287      */
56288     minButtonWidth: 75,
56289
56290     /**
56291      * @cfg {Boolean} collapsed
56292      * `true` to render the panel collapsed, `false` to render it expanded.
56293      */
56294     collapsed: false,
56295
56296     /**
56297      * @cfg {Boolean} collapseFirst
56298      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
56299      * the panel's title bar, `false` to render it last.
56300      */
56301     collapseFirst: true,
56302
56303     /**
56304      * @cfg {Boolean} hideCollapseTool
56305      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
56306      */
56307     hideCollapseTool: false,
56308
56309     /**
56310      * @cfg {Boolean} titleCollapse
56311      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
56312      * the header bar, `false`) to allow it only by clicking to tool butto).
56313      */
56314     titleCollapse: false,
56315
56316     /**
56317      * @cfg {String} collapseMode
56318      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
56319      * {@link Ext.layout.container.Border border layout}.**
56320      *
56321      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
56322      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
56323      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
56324      * expand tool.
56325      *
56326      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
56327      *
56328      * - **`undefined/omitted`**
56329      *
56330      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
56331      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
56332      *
56333      * - **`header`** :
56334      *
56335      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
56336      *   layout}.
56337      */
56338
56339     /**
56340      * @cfg {Ext.Component/Object} placeholder
56341      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56342      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
56343      *
56344      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
56345      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
56346      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
56347      */
56348
56349     /**
56350      * @cfg {Boolean} floatable
56351      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56352      * {@link Ext.layout.container.Border border layout}.**
56353      *
56354      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
56355      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
56356      */
56357     floatable: true,
56358
56359     /**
56360      * @cfg {Boolean} overlapHeader
56361      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
56362      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
56363      * which does not have frame:true, this will need to be set to true.
56364      */
56365
56366     /**
56367      * @cfg {Boolean} collapsible
56368      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
56369      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
56370      *
56371      * See {@link #collapseMode} and {@link #collapseDirection}
56372      */
56373     collapsible: false,
56374
56375     /**
56376      * @cfg {Boolean} collapseDirection
56377      * The direction to collapse the Panel when the toggle button is clicked.
56378      *
56379      * Defaults to the {@link #headerPosition}
56380      *
56381      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
56382      * Ext.layout.container.Border border layout}.**
56383      *
56384      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56385      */
56386
56387     /**
56388      * @cfg {Boolean} closable
56389      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
56390      * disallow closing the window.
56391      *
56392      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
56393      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
56394      * reused.
56395      *
56396      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
56397      */
56398     closable: false,
56399
56400     /**
56401      * @cfg {String} closeAction
56402      * The action to take when the close header tool is clicked:
56403      *
56404      * - **`'{@link #destroy}'`** :
56405      *
56406      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
56407      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
56408      *
56409      * - **`'{@link #hide}'`** :
56410      *
56411      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
56412      *   available to be redisplayed via the {@link #show} method.
56413      *
56414      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
56415      * approriate closeAction.
56416      */
56417     closeAction: 'destroy',
56418
56419     /**
56420      * @cfg {Object/Object[]} dockedItems
56421      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
56422      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
56423      *
56424      *     var panel = new Ext.panel.Panel({
56425      *         dockedItems: [{
56426      *             xtype: 'toolbar',
56427      *             dock: 'top',
56428      *             items: [{
56429      *                 text: 'Docked to the top'
56430      *             }]
56431      *         }]
56432      *     });
56433      */
56434
56435     /**
56436       * @cfg {Boolean} preventHeader
56437       * Prevent a Header from being created and shown.
56438       */
56439     preventHeader: false,
56440
56441      /**
56442       * @cfg {String} headerPosition
56443       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56444       */
56445     headerPosition: 'top',
56446
56447      /**
56448      * @cfg {Boolean} frame
56449      * True to apply a frame to the panel.
56450      */
56451     frame: false,
56452
56453     /**
56454      * @cfg {Boolean} frameHeader
56455      * True to apply a frame to the panel panels header (if 'frame' is true).
56456      */
56457     frameHeader: true,
56458
56459     /**
56460      * @cfg {Object[]/Ext.panel.Tool[]} tools
56461      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
56462      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
56463      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
56464      *
56465      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
56466      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
56467      * behavior.
56468      *
56469      * Example usage:
56470      *
56471      *     tools:[{
56472      *         type:'refresh',
56473      *         tooltip: 'Refresh form Data',
56474      *         // hidden:true,
56475      *         handler: function(event, toolEl, panel){
56476      *             // refresh logic
56477      *         }
56478      *     },
56479      *     {
56480      *         type:'help',
56481      *         tooltip: 'Get Help',
56482      *         handler: function(event, toolEl, panel){
56483      *             // show help here
56484      *         }
56485      *     }]
56486      */
56487
56488     /**
56489      * @cfg {String} [title='']
56490      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
56491      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
56492      * {@link #preventHeader} is set to `true`.
56493      */
56494
56495     /**
56496      * @cfg {String} iconCls
56497      * CSS class for icon in header. Used for displaying an icon to the left of a title.
56498      */
56499
56500     initComponent: function() {
56501         var me = this,
56502             cls;
56503
56504         me.addEvents(
56505
56506             /**
56507              * @event beforeclose
56508              * Fires before the user closes the panel. Return false from any listener to stop the close event being
56509              * fired
56510              * @param {Ext.panel.Panel} panel The Panel object
56511              */
56512             'beforeclose',
56513
56514             /**
56515              * @event beforeexpand
56516              * Fires before this panel is expanded. Return false to prevent the expand.
56517              * @param {Ext.panel.Panel} p The Panel being expanded.
56518              * @param {Boolean} animate True if the expand is animated, else false.
56519              */
56520             "beforeexpand",
56521
56522             /**
56523              * @event beforecollapse
56524              * Fires before this panel is collapsed. Return false to prevent the collapse.
56525              * @param {Ext.panel.Panel} p The Panel being collapsed.
56526              * @param {String} direction . The direction of the collapse. One of
56527              *
56528              *   - Ext.Component.DIRECTION_TOP
56529              *   - Ext.Component.DIRECTION_RIGHT
56530              *   - Ext.Component.DIRECTION_BOTTOM
56531              *   - Ext.Component.DIRECTION_LEFT
56532              *
56533              * @param {Boolean} animate True if the collapse is animated, else false.
56534              */
56535             "beforecollapse",
56536
56537             /**
56538              * @event expand
56539              * Fires after this Panel has expanded.
56540              * @param {Ext.panel.Panel} p The Panel that has been expanded.
56541              */
56542             "expand",
56543
56544             /**
56545              * @event collapse
56546              * Fires after this Panel hass collapsed.
56547              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
56548              */
56549             "collapse",
56550
56551             /**
56552              * @event titlechange
56553              * Fires after the Panel title has been set or changed.
56554              * @param {Ext.panel.Panel} p the Panel which has been resized.
56555              * @param {String} newTitle The new title.
56556              * @param {String} oldTitle The previous panel title.
56557              */
56558             'titlechange',
56559
56560             /**
56561              * @event iconchange
56562              * Fires after the Panel iconCls has been set or changed.
56563              * @param {Ext.panel.Panel} p the Panel which has been resized.
56564              * @param {String} newIconCls The new iconCls.
56565              * @param {String} oldIconCls The previous panel iconCls.
56566              */
56567             'iconchange'
56568         );
56569
56570         // Save state on these two events.
56571         this.addStateEvents('expand', 'collapse');
56572
56573         if (me.unstyled) {
56574             me.setUI('plain');
56575         }
56576
56577         if (me.frame) {
56578             me.setUI(me.ui + '-framed');
56579         }
56580
56581         // Backwards compatibility
56582         me.bridgeToolbars();
56583
56584         me.callParent();
56585         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
56586     },
56587
56588     setBorder: function(border) {
56589         // var me     = this,
56590         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
56591         //
56592         // me.callParent(arguments);
56593         //
56594         // if (me.collapsed) {
56595         //     me[method](me.collapsedCls + '-noborder');
56596         // }
56597         //
56598         // if (me.header) {
56599         //     me.header.setBorder(border);
56600         //     if (me.collapsed) {
56601         //         me.header[method](me.collapsedCls + '-noborder');
56602         //     }
56603         // }
56604
56605         this.callParent(arguments);
56606     },
56607
56608     beforeDestroy: function() {
56609         Ext.destroy(
56610             this.ghostPanel,
56611             this.dd
56612         );
56613         this.callParent();
56614     },
56615
56616     initAria: function() {
56617         this.callParent();
56618         this.initHeaderAria();
56619     },
56620
56621     initHeaderAria: function() {
56622         var me = this,
56623             el = me.el,
56624             header = me.header;
56625         if (el && header) {
56626             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
56627         }
56628     },
56629
56630     getHeader: function() {
56631         return this.header;
56632     },
56633
56634     /**
56635      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
56636      * @param {String} newTitle
56637      */
56638     setTitle: function(newTitle) {
56639         var me = this,
56640         oldTitle = this.title;
56641
56642         me.title = newTitle;
56643         if (me.header) {
56644             me.header.setTitle(newTitle);
56645         } else {
56646             me.updateHeader();
56647         }
56648
56649         if (me.reExpander) {
56650             me.reExpander.setTitle(newTitle);
56651         }
56652         me.fireEvent('titlechange', me, newTitle, oldTitle);
56653     },
56654
56655     /**
56656      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
56657      * {@link #iconchange} event after completion.
56658      * @param {String} newIconCls The new CSS class name
56659      */
56660     setIconCls: function(newIconCls) {
56661         var me = this,
56662             oldIconCls = me.iconCls;
56663
56664         me.iconCls = newIconCls;
56665         var header = me.header;
56666         if (header) {
56667             header.setIconCls(newIconCls);
56668         }
56669         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
56670     },
56671
56672     bridgeToolbars: function() {
56673         var me = this,
56674             docked = [],
56675             fbar,
56676             fbarDefaults,
56677             minButtonWidth = me.minButtonWidth;
56678
56679         function initToolbar (toolbar, pos, useButtonAlign) {
56680             if (Ext.isArray(toolbar)) {
56681                 toolbar = {
56682                     xtype: 'toolbar',
56683                     items: toolbar
56684                 };
56685             }
56686             else if (!toolbar.xtype) {
56687                 toolbar.xtype = 'toolbar';
56688             }
56689             toolbar.dock = pos;
56690             if (pos == 'left' || pos == 'right') {
56691                 toolbar.vertical = true;
56692             }
56693
56694             // Legacy support for buttonAlign (only used by buttons/fbar)
56695             if (useButtonAlign) {
56696                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
56697                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
56698                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
56699                 });
56700             }
56701             return toolbar;
56702         }
56703
56704         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
56705
56706         /**
56707          * @cfg {String} buttonAlign
56708          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
56709          * 'right' for buttons/fbar, 'left' for other toolbar types).
56710          *
56711          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
56712          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
56713          */
56714
56715         /**
56716          * @cfg {Object/Object[]} tbar
56717          * Convenience config. Short for 'Top Bar'.
56718          *
56719          *     tbar: [
56720          *       { xtype: 'button', text: 'Button 1' }
56721          *     ]
56722          *
56723          * is equivalent to
56724          *
56725          *     dockedItems: [{
56726          *         xtype: 'toolbar',
56727          *         dock: 'top',
56728          *         items: [
56729          *             { xtype: 'button', text: 'Button 1' }
56730          *         ]
56731          *     }]
56732          */
56733         if (me.tbar) {
56734             docked.push(initToolbar(me.tbar, 'top'));
56735             me.tbar = null;
56736         }
56737
56738         /**
56739          * @cfg {Object/Object[]} bbar
56740          * Convenience config. Short for 'Bottom Bar'.
56741          *
56742          *     bbar: [
56743          *       { xtype: 'button', text: 'Button 1' }
56744          *     ]
56745          *
56746          * is equivalent to
56747          *
56748          *     dockedItems: [{
56749          *         xtype: 'toolbar',
56750          *         dock: 'bottom',
56751          *         items: [
56752          *             { xtype: 'button', text: 'Button 1' }
56753          *         ]
56754          *     }]
56755          */
56756         if (me.bbar) {
56757             docked.push(initToolbar(me.bbar, 'bottom'));
56758             me.bbar = null;
56759         }
56760
56761         /**
56762          * @cfg {Object/Object[]} buttons
56763          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
56764          * synonym for the {@link #fbar} config.
56765          *
56766          *     buttons: [
56767          *       { text: 'Button 1' }
56768          *     ]
56769          *
56770          * is equivalent to
56771          *
56772          *     dockedItems: [{
56773          *         xtype: 'toolbar',
56774          *         dock: 'bottom',
56775          *         ui: 'footer',
56776          *         defaults: {minWidth: {@link #minButtonWidth}},
56777          *         items: [
56778          *             { xtype: 'component', flex: 1 },
56779          *             { xtype: 'button', text: 'Button 1' }
56780          *         ]
56781          *     }]
56782          *
56783          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56784          * each of the buttons in the buttons toolbar.
56785          */
56786         if (me.buttons) {
56787             me.fbar = me.buttons;
56788             me.buttons = null;
56789         }
56790
56791         /**
56792          * @cfg {Object/Object[]} fbar
56793          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
56794          *
56795          *     fbar: [
56796          *       { type: 'button', text: 'Button 1' }
56797          *     ]
56798          *
56799          * is equivalent to
56800          *
56801          *     dockedItems: [{
56802          *         xtype: 'toolbar',
56803          *         dock: 'bottom',
56804          *         ui: 'footer',
56805          *         defaults: {minWidth: {@link #minButtonWidth}},
56806          *         items: [
56807          *             { xtype: 'component', flex: 1 },
56808          *             { xtype: 'button', text: 'Button 1' }
56809          *         ]
56810          *     }]
56811          *
56812          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56813          * each of the buttons in the fbar.
56814          */
56815         if (me.fbar) {
56816             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
56817             fbar.ui = 'footer';
56818
56819             // Apply the minButtonWidth config to buttons in the toolbar
56820             if (minButtonWidth) {
56821                 fbarDefaults = fbar.defaults;
56822                 fbar.defaults = function(config) {
56823                     var defaults = fbarDefaults || {};
56824                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
56825                             !('minWidth' in defaults)) {
56826                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
56827                     }
56828                     return defaults;
56829                 };
56830             }
56831
56832             docked.push(fbar);
56833             me.fbar = null;
56834         }
56835
56836         /**
56837          * @cfg {Object/Object[]} lbar
56838          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
56839          *
56840          *     lbar: [
56841          *       { xtype: 'button', text: 'Button 1' }
56842          *     ]
56843          *
56844          * is equivalent to
56845          *
56846          *     dockedItems: [{
56847          *         xtype: 'toolbar',
56848          *         dock: 'left',
56849          *         items: [
56850          *             { xtype: 'button', text: 'Button 1' }
56851          *         ]
56852          *     }]
56853          */
56854         if (me.lbar) {
56855             docked.push(initToolbar(me.lbar, 'left'));
56856             me.lbar = null;
56857         }
56858
56859         /**
56860          * @cfg {Object/Object[]} rbar
56861          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
56862          *
56863          *     rbar: [
56864          *       { xtype: 'button', text: 'Button 1' }
56865          *     ]
56866          *
56867          * is equivalent to
56868          *
56869          *     dockedItems: [{
56870          *         xtype: 'toolbar',
56871          *         dock: 'right',
56872          *         items: [
56873          *             { xtype: 'button', text: 'Button 1' }
56874          *         ]
56875          *     }]
56876          */
56877         if (me.rbar) {
56878             docked.push(initToolbar(me.rbar, 'right'));
56879             me.rbar = null;
56880         }
56881
56882         if (me.dockedItems) {
56883             if (!Ext.isArray(me.dockedItems)) {
56884                 me.dockedItems = [me.dockedItems];
56885             }
56886             me.dockedItems = me.dockedItems.concat(docked);
56887         } else {
56888             me.dockedItems = docked;
56889         }
56890     },
56891
56892     /**
56893      * @private
56894      * Tools are a Panel-specific capabilty.
56895      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
56896      */
56897     initTools: function() {
56898         var me = this;
56899
56900         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
56901
56902         // Add a collapse tool unless configured to not show a collapse tool
56903         // or to not even show a header.
56904         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
56905             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
56906             me.collapseTool = me.expandTool = me.createComponent({
56907                 xtype: 'tool',
56908                 type: 'collapse-' + me.collapseDirection,
56909                 expandType: me.getOppositeDirection(me.collapseDirection),
56910                 handler: me.toggleCollapse,
56911                 scope: me
56912             });
56913
56914             // Prepend collapse tool is configured to do so.
56915             if (me.collapseFirst) {
56916                 me.tools.unshift(me.collapseTool);
56917             }
56918         }
56919
56920         // Add subclass-specific tools.
56921         me.addTools();
56922
56923         // Make Panel closable.
56924         if (me.closable) {
56925             me.addClsWithUI('closable');
56926             me.addTool({
56927                 type: 'close',
56928                 handler: Ext.Function.bind(me.close, this, [])
56929             });
56930         }
56931
56932         // Append collapse tool if needed.
56933         if (me.collapseTool && !me.collapseFirst) {
56934             me.tools.push(me.collapseTool);
56935         }
56936     },
56937
56938     /**
56939      * @private
56940      * @template
56941      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
56942      */
56943     addTools: Ext.emptyFn,
56944
56945     /**
56946      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
56947      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
56948      * close happens and will cancel the close action if it returns false.
56949      *
56950      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
56951      * {@link #destroy} and {@link #hide} methods.
56952      */
56953     close: function() {
56954         if (this.fireEvent('beforeclose', this) !== false) {
56955             this.doClose();
56956         }
56957     },
56958
56959     // private
56960     doClose: function() {
56961         this.fireEvent('close', this);
56962         this[this.closeAction]();
56963     },
56964
56965     onRender: function(ct, position) {
56966         var me = this,
56967             topContainer;
56968
56969         // Add class-specific header tools.
56970         // Panel adds collapsible and closable.
56971         me.initTools();
56972
56973         // Dock the header/title
56974         me.updateHeader();
56975
56976         // Call to super after adding the header, to prevent an unnecessary re-layout
56977         me.callParent(arguments);
56978     },
56979
56980     afterRender: function() {
56981         var me = this;
56982
56983         me.callParent(arguments);
56984
56985         // Instate the collapsed state after render. We need to wait for
56986         // this moment so that we have established at least some of our size (from our
56987         // configured dimensions or from content via the component layout)
56988         if (me.collapsed) {
56989             me.collapsed = false;
56990             me.collapse(null, false, true);
56991         }
56992     },
56993
56994     /**
56995      * Create, hide, or show the header component as appropriate based on the current config.
56996      * @private
56997      * @param {Boolean} force True to force the header to be created
56998      */
56999     updateHeader: function(force) {
57000         var me = this,
57001             header = me.header,
57002             title = me.title,
57003             tools = me.tools;
57004
57005         if (!me.preventHeader && (force || title || (tools && tools.length))) {
57006             if (!header) {
57007                 header = me.header = Ext.create('Ext.panel.Header', {
57008                     title       : title,
57009                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
57010                     dock        : me.headerPosition || 'top',
57011                     textCls     : me.headerTextCls,
57012                     iconCls     : me.iconCls,
57013                     baseCls     : me.baseCls + '-header',
57014                     tools       : tools,
57015                     ui          : me.ui,
57016                     indicateDrag: me.draggable,
57017                     border      : me.border,
57018                     frame       : me.frame && me.frameHeader,
57019                     ignoreParentFrame : me.frame || me.overlapHeader,
57020                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
57021                     listeners   : me.collapsible && me.titleCollapse ? {
57022                         click: me.toggleCollapse,
57023                         scope: me
57024                     } : null
57025                 });
57026                 me.addDocked(header, 0);
57027
57028                 // Reference the Header's tool array.
57029                 // Header injects named references.
57030                 me.tools = header.tools;
57031             }
57032             header.show();
57033             me.initHeaderAria();
57034         } else if (header) {
57035             header.hide();
57036         }
57037     },
57038
57039     // inherit docs
57040     setUI: function(ui) {
57041         var me = this;
57042
57043         me.callParent(arguments);
57044
57045         if (me.header) {
57046             me.header.setUI(ui);
57047         }
57048     },
57049
57050     // private
57051     getContentTarget: function() {
57052         return this.body;
57053     },
57054
57055     getTargetEl: function() {
57056         return this.body || this.frameBody || this.el;
57057     },
57058
57059     // the overrides below allow for collapsed regions inside the border layout to be hidden
57060
57061     // inherit docs
57062     isVisible: function(deep){
57063         var me = this;
57064         if (me.collapsed && me.placeholder) {
57065             return me.placeholder.isVisible(deep);
57066         }
57067         return me.callParent(arguments);
57068     },
57069
57070     // inherit docs
57071     onHide: function(){
57072         var me = this;
57073         if (me.collapsed && me.placeholder) {
57074             me.placeholder.hide();
57075         } else {
57076             me.callParent(arguments);
57077         }
57078     },
57079
57080     // inherit docs
57081     onShow: function(){
57082         var me = this;
57083         if (me.collapsed && me.placeholder) {
57084             // force hidden back to true, since this gets set by the layout
57085             me.hidden = true;
57086             me.placeholder.show();
57087         } else {
57088             me.callParent(arguments);
57089         }
57090     },
57091
57092     addTool: function(tool) {
57093         var me = this,
57094             header = me.header;
57095
57096         if (Ext.isArray(tool)) {
57097             Ext.each(tool, me.addTool, me);
57098             return;
57099         }
57100         me.tools.push(tool);
57101         if (header) {
57102             header.addTool(tool);
57103         }
57104         me.updateHeader();
57105     },
57106
57107     getOppositeDirection: function(d) {
57108         var c = Ext.Component;
57109         switch (d) {
57110             case c.DIRECTION_TOP:
57111                 return c.DIRECTION_BOTTOM;
57112             case c.DIRECTION_RIGHT:
57113                 return c.DIRECTION_LEFT;
57114             case c.DIRECTION_BOTTOM:
57115                 return c.DIRECTION_TOP;
57116             case c.DIRECTION_LEFT:
57117                 return c.DIRECTION_RIGHT;
57118         }
57119     },
57120
57121     /**
57122      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
57123      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
57124      * collapse action if it returns false.
57125      *
57126      * @param {String} direction . The direction to collapse towards. Must be one of
57127      *
57128      *   - Ext.Component.DIRECTION_TOP
57129      *   - Ext.Component.DIRECTION_RIGHT
57130      *   - Ext.Component.DIRECTION_BOTTOM
57131      *   - Ext.Component.DIRECTION_LEFT
57132      *
57133      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57134      * {@link #animCollapse} panel config)
57135      * @return {Ext.panel.Panel} this
57136      */
57137     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
57138         var me = this,
57139             c = Ext.Component,
57140             height = me.getHeight(),
57141             width = me.getWidth(),
57142             frameInfo,
57143             newSize = 0,
57144             dockedItems = me.dockedItems.items,
57145             dockedItemCount = dockedItems.length,
57146             i = 0,
57147             comp,
57148             pos,
57149             anim = {
57150                 from: {
57151                     height: height,
57152                     width: width
57153                 },
57154                 to: {
57155                     height: height,
57156                     width: width
57157                 },
57158                 listeners: {
57159                     afteranimate: me.afterCollapse,
57160                     scope: me
57161                 },
57162                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
57163             },
57164             reExpander,
57165             reExpanderOrientation,
57166             reExpanderDock,
57167             getDimension,
57168             collapseDimension;
57169
57170         if (!direction) {
57171             direction = me.collapseDirection;
57172         }
57173
57174         // If internal (Called because of initial collapsed state), then no animation, and no events.
57175         if (internal) {
57176             animate = false;
57177         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
57178             return false;
57179         }
57180
57181         reExpanderDock = direction;
57182         me.expandDirection = me.getOppositeDirection(direction);
57183
57184         // Track docked items which we hide during collapsed state
57185         me.hiddenDocked = [];
57186
57187         switch (direction) {
57188             case c.DIRECTION_TOP:
57189             case c.DIRECTION_BOTTOM:
57190                 reExpanderOrientation = 'horizontal';
57191                 collapseDimension = 'height';
57192                 getDimension = 'getHeight';
57193
57194                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
57195                 // Also, collect all other docked items which we must hide after collapse.
57196                 for (; i < dockedItemCount; i++) {
57197                     comp = dockedItems[i];
57198                     if (comp.isVisible()) {
57199                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
57200                             reExpander = comp;
57201                         } else {
57202                             me.hiddenDocked.push(comp);
57203                         }
57204                     } else if (comp === me.reExpander) {
57205                         reExpander = comp;
57206                     }
57207                 }
57208
57209                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
57210                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57211                     anim.from.top = pos;
57212                 }
57213                 break;
57214
57215             case c.DIRECTION_LEFT:
57216             case c.DIRECTION_RIGHT:
57217                 reExpanderOrientation = 'vertical';
57218                 collapseDimension = 'width';
57219                 getDimension = 'getWidth';
57220
57221                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
57222                 // Also, collect all other docked items which we must hide after collapse.
57223                 for (; i < dockedItemCount; i++) {
57224                     comp = dockedItems[i];
57225                     if (comp.isVisible()) {
57226                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
57227                             reExpander = comp;
57228                         } else {
57229                             me.hiddenDocked.push(comp);
57230                         }
57231                     } else if (comp === me.reExpander) {
57232                         reExpander = comp;
57233                     }
57234                 }
57235
57236                 if (direction == Ext.Component.DIRECTION_RIGHT) {
57237                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57238                     anim.from.left = pos;
57239                 }
57240                 break;
57241
57242             default:
57243                 throw('Panel collapse must be passed a valid Component collapse direction');
57244         }
57245
57246         // Disable toggle tool during animated collapse
57247         if (animate && me.collapseTool) {
57248             me.collapseTool.disable();
57249         }
57250
57251         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
57252         me.addClsWithUI(me.collapsedCls);
57253         // if (me.border === false) {
57254         //     me.addClsWithUI(me.collapsedCls + '-noborder');
57255         // }
57256
57257         // We found a header: Measure it to find the collapse-to size.
57258         if (reExpander && reExpander.rendered) {
57259
57260             //we must add the collapsed cls to the header and then remove to get the proper height
57261             reExpander.addClsWithUI(me.collapsedCls);
57262             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57263             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57264                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57265             }
57266
57267             frameInfo = reExpander.getFrameInfo();
57268
57269             //get the size
57270             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
57271
57272             //and remove
57273             reExpander.removeClsWithUI(me.collapsedCls);
57274             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57275             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57276                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57277             }
57278         }
57279         // No header: Render and insert a temporary one, and then measure it.
57280         else {
57281             reExpander = {
57282                 hideMode: 'offsets',
57283                 temporary: true,
57284                 title: me.title,
57285                 orientation: reExpanderOrientation,
57286                 dock: reExpanderDock,
57287                 textCls: me.headerTextCls,
57288                 iconCls: me.iconCls,
57289                 baseCls: me.baseCls + '-header',
57290                 ui: me.ui,
57291                 frame: me.frame && me.frameHeader,
57292                 ignoreParentFrame: me.frame || me.overlapHeader,
57293                 indicateDrag: me.draggable,
57294                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
57295                 renderTo: me.el
57296             };
57297             if (!me.hideCollapseTool) {
57298                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
57299                     xtype: 'tool',
57300                     type: 'expand-' + me.expandDirection,
57301                     handler: me.toggleCollapse,
57302                     scope: me
57303                 }];
57304             }
57305
57306             // Capture the size of the re-expander.
57307             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
57308             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
57309             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
57310             reExpander.hide();
57311
57312             // Insert the new docked item
57313             me.insertDocked(0, reExpander);
57314         }
57315
57316         me.reExpander = reExpander;
57317         me.reExpander.addClsWithUI(me.collapsedCls);
57318         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57319         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57320             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57321         }
57322
57323         // If collapsing right or down, we'll be also animating the left or top.
57324         if (direction == Ext.Component.DIRECTION_RIGHT) {
57325             anim.to.left = pos + (width - newSize);
57326         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
57327             anim.to.top = pos + (height - newSize);
57328         }
57329
57330         // Animate to the new size
57331         anim.to[collapseDimension] = newSize;
57332
57333         // When we collapse a panel, the panel is in control of one dimension (depending on
57334         // collapse direction) and sets that on the component. We must restore the user's
57335         // original value (including non-existance) when we expand. Using this technique, we
57336         // mimic setCalculatedSize for the dimension we do not control and setSize for the
57337         // one we do (only while collapsed).
57338         if (!me.collapseMemento) {
57339             me.collapseMemento = new Ext.util.Memento(me);
57340         }
57341         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
57342
57343         // Remove any flex config before we attempt to collapse.
57344         me.savedFlex = me.flex;
57345         me.minWidth = 0;
57346         me.minHeight = 0;
57347         delete me.flex;
57348         me.suspendLayout = true;
57349
57350         if (animate) {
57351             me.animate(anim);
57352         } else {
57353             me.setSize(anim.to.width, anim.to.height);
57354             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
57355                 me.setPosition(anim.to.left, anim.to.top);
57356             }
57357             me.afterCollapse(false, internal);
57358         }
57359         return me;
57360     },
57361
57362     afterCollapse: function(animated, internal) {
57363         var me = this,
57364             i = 0,
57365             l = me.hiddenDocked.length;
57366
57367         me.collapseMemento.restore(['minWidth', 'minHeight']);
57368
57369         // Now we can restore the dimension we don't control to its original state
57370         // Leave the value in the memento so that it can be correctly restored
57371         // if it is set by animation.
57372         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
57373             me.layoutManagedHeight = 2;
57374             me.collapseMemento.restore('width', false);
57375         } else {
57376             me.layoutManagedWidth = 2;
57377             me.collapseMemento.restore('height', false);
57378         }
57379
57380         // We must hide the body, otherwise it overlays docked items which come before
57381         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
57382         me.saveScrollTop = me.body.dom.scrollTop;
57383         me.body.setStyle('display', 'none');
57384
57385         for (; i < l; i++) {
57386             me.hiddenDocked[i].hide();
57387         }
57388         if (me.reExpander) {
57389             me.reExpander.updateFrame();
57390             me.reExpander.show();
57391         }
57392         me.collapsed = true;
57393         me.suspendLayout = false;
57394
57395         if (!internal) {
57396             if (me.ownerCt) {
57397                 // Because Component layouts only inform upstream containers if they have changed size,
57398                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
57399                 if (animated) {
57400                     me.ownerCt.layout.layout();
57401                 }
57402             } else if (me.reExpander.temporary) {
57403                 me.doComponentLayout();
57404             }
57405         }
57406
57407         if (me.resizer) {
57408             me.resizer.disable();
57409         }
57410
57411         // If me Panel was configured with a collapse tool in its header, flip it's type
57412         if (me.collapseTool) {
57413             me.collapseTool.setType('expand-' + me.expandDirection);
57414         }
57415         if (!internal) {
57416             me.fireEvent('collapse', me);
57417         }
57418
57419         // Re-enable the toggle tool after an animated collapse
57420         if (animated && me.collapseTool) {
57421             me.collapseTool.enable();
57422         }
57423     },
57424
57425     /**
57426      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
57427      * expand action if it returns false.
57428      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57429      * {@link #animCollapse} panel config)
57430      * @return {Ext.panel.Panel} this
57431      */
57432     expand: function(animate) {
57433         var me = this;
57434         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
57435             return false;
57436         }
57437
57438         var i = 0,
57439             l = me.hiddenDocked.length,
57440             direction = me.expandDirection,
57441             height = me.getHeight(),
57442             width = me.getWidth(),
57443             pos, anim;
57444
57445         // Disable toggle tool during animated expand
57446         if (animate && me.collapseTool) {
57447             me.collapseTool.disable();
57448         }
57449
57450         // Show any docked items that we hid on collapse
57451         // And hide the injected reExpander Header
57452         for (; i < l; i++) {
57453             me.hiddenDocked[i].hidden = false;
57454             me.hiddenDocked[i].el.show();
57455         }
57456         if (me.reExpander) {
57457             if (me.reExpander.temporary) {
57458                 me.reExpander.hide();
57459             } else {
57460                 me.reExpander.removeClsWithUI(me.collapsedCls);
57461                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
57462                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57463                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57464                 }
57465                 me.reExpander.updateFrame();
57466             }
57467         }
57468
57469         // If me Panel was configured with a collapse tool in its header, flip it's type
57470         if (me.collapseTool) {
57471             me.collapseTool.setType('collapse-' + me.collapseDirection);
57472         }
57473
57474         // Restore body display and scroll position
57475         me.body.setStyle('display', '');
57476         me.body.dom.scrollTop = me.saveScrollTop;
57477
57478         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
57479         me.collapsed = false;
57480
57481         // Remove any collapsed styling before any animation begins
57482         me.removeClsWithUI(me.collapsedCls);
57483         // if (me.border === false) {
57484         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
57485         // }
57486
57487         anim = {
57488             to: {
57489             },
57490             from: {
57491                 height: height,
57492                 width: width
57493             },
57494             listeners: {
57495                 afteranimate: me.afterExpand,
57496                 scope: me
57497             }
57498         };
57499
57500         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
57501
57502             // Restore the collapsed dimension.
57503             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57504             me.collapseMemento.restore('height', false);
57505
57506             // If autoHeight, measure the height now we have shown the body element.
57507             if (me.height === undefined) {
57508                 me.setCalculatedSize(me.width, null);
57509                 anim.to.height = me.getHeight();
57510
57511                 // Must size back down to collapsed for the animation.
57512                 me.setCalculatedSize(me.width, anim.from.height);
57513             }
57514             // If we were flexed, then we can't just restore to the saved size.
57515             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57516             else if (me.savedFlex) {
57517                 me.flex = me.savedFlex;
57518                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
57519                 delete me.flex;
57520             }
57521             // Else, restore to saved height
57522             else {
57523                 anim.to.height = me.height;
57524             }
57525
57526             // top needs animating upwards
57527             if (direction == Ext.Component.DIRECTION_TOP) {
57528                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57529                 anim.from.top = pos;
57530                 anim.to.top = pos - (anim.to.height - height);
57531             }
57532         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
57533
57534             // Restore the collapsed dimension.
57535             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57536             me.collapseMemento.restore('width', false);
57537
57538             // If autoWidth, measure the width now we have shown the body element.
57539             if (me.width === undefined) {
57540                 me.setCalculatedSize(null, me.height);
57541                 anim.to.width = me.getWidth();
57542
57543                 // Must size back down to collapsed for the animation.
57544                 me.setCalculatedSize(anim.from.width, me.height);
57545             }
57546             // If we were flexed, then we can't just restore to the saved size.
57547             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57548             else if (me.savedFlex) {
57549                 me.flex = me.savedFlex;
57550                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
57551                 delete me.flex;
57552             }
57553             // Else, restore to saved width
57554             else {
57555                 anim.to.width = me.width;
57556             }
57557
57558             // left needs animating leftwards
57559             if (direction == Ext.Component.DIRECTION_LEFT) {
57560                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57561                 anim.from.left = pos;
57562                 anim.to.left = pos - (anim.to.width - width);
57563             }
57564         }
57565
57566         if (animate) {
57567             me.animate(anim);
57568         } else {
57569             me.setCalculatedSize(anim.to.width, anim.to.height);
57570             if (anim.to.x) {
57571                 me.setLeft(anim.to.x);
57572             }
57573             if (anim.to.y) {
57574                 me.setTop(anim.to.y);
57575             }
57576             me.afterExpand(false);
57577         }
57578
57579         return me;
57580     },
57581
57582     afterExpand: function(animated) {
57583         var me = this;
57584
57585         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
57586         if (me.savedFlex) {
57587             me.flex = me.savedFlex;
57588             delete me.savedFlex;
57589             delete me.width;
57590             delete me.height;
57591         }
57592
57593         // Restore width/height and dimension management flags to original values
57594         if (me.collapseMemento) {
57595             me.collapseMemento.restoreAll();
57596         }
57597
57598         if (animated && me.ownerCt) {
57599             // IE 6 has an intermittent repaint issue in this case so give
57600             // it a little extra time to catch up before laying out.
57601             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
57602         }
57603
57604         if (me.resizer) {
57605             me.resizer.enable();
57606         }
57607
57608         me.fireEvent('expand', me);
57609
57610         // Re-enable the toggle tool after an animated expand
57611         if (animated && me.collapseTool) {
57612             me.collapseTool.enable();
57613         }
57614     },
57615
57616     /**
57617      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
57618      * @return {Ext.panel.Panel} this
57619      */
57620     toggleCollapse: function() {
57621         if (this.collapsed) {
57622             this.expand(this.animCollapse);
57623         } else {
57624             this.collapse(this.collapseDirection, this.animCollapse);
57625         }
57626         return this;
57627     },
57628
57629     // private
57630     getKeyMap : function(){
57631         if(!this.keyMap){
57632             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
57633         }
57634         return this.keyMap;
57635     },
57636
57637     // private
57638     initDraggable : function(){
57639         /**
57640          * @property {Ext.dd.DragSource} dd
57641          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
57642          * Ext.dd.DragSource} which handles dragging the Panel.
57643          *
57644          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
57645          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
57646          */
57647         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
57648     },
57649
57650     // private - helper function for ghost
57651     ghostTools : function() {
57652         var tools = [],
57653             headerTools = this.header.query('tool[hidden=false]');
57654
57655         if (headerTools.length) {
57656             Ext.each(headerTools, function(tool) {
57657                 // Some tools can be full components, and copying them into the ghost
57658                 // actually removes them from the owning panel. You could also potentially
57659                 // end up with duplicate DOM ids as well. To avoid any issues we just make
57660                 // a simple bare-minimum clone of each tool for ghosting purposes.
57661                 tools.push({
57662                     type: tool.type
57663                 });
57664             });
57665         } else {
57666             tools = [{
57667                 type: 'placeholder'
57668             }];
57669         }
57670         return tools;
57671     },
57672
57673     // private - used for dragging
57674     ghost: function(cls) {
57675         var me = this,
57676             ghostPanel = me.ghostPanel,
57677             box = me.getBox(),
57678             header;
57679
57680         if (!ghostPanel) {
57681             ghostPanel = Ext.create('Ext.panel.Panel', {
57682                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
57683                 floating: {
57684                     shadow: false
57685                 },
57686                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
57687                 overlapHeader: me.overlapHeader,
57688                 headerPosition: me.headerPosition,
57689                 baseCls: me.baseCls,
57690                 cls: me.baseCls + '-ghost ' + (cls ||'')
57691             });
57692             me.ghostPanel = ghostPanel;
57693         }
57694         ghostPanel.floatParent = me.floatParent;
57695         if (me.floating) {
57696             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
57697         } else {
57698             ghostPanel.toFront();
57699         }
57700         header = ghostPanel.header;
57701         // restore options
57702         if (header) {
57703             header.suspendLayout = true;
57704             Ext.Array.forEach(header.query('tool'), function(tool){
57705                 header.remove(tool);
57706             });
57707             header.suspendLayout = false;
57708         }
57709         ghostPanel.addTool(me.ghostTools());
57710         ghostPanel.setTitle(me.title);
57711         ghostPanel.setIconCls(me.iconCls);
57712
57713         ghostPanel.el.show();
57714         ghostPanel.setPosition(box.x, box.y);
57715         ghostPanel.setSize(box.width, box.height);
57716         me.el.hide();
57717         if (me.floatingItems) {
57718             me.floatingItems.hide();
57719         }
57720         return ghostPanel;
57721     },
57722
57723     // private
57724     unghost: function(show, matchPosition) {
57725         var me = this;
57726         if (!me.ghostPanel) {
57727             return;
57728         }
57729         if (show !== false) {
57730             me.el.show();
57731             if (matchPosition !== false) {
57732                 me.setPosition(me.ghostPanel.getPosition());
57733             }
57734             if (me.floatingItems) {
57735                 me.floatingItems.show();
57736             }
57737             Ext.defer(me.focus, 10, me);
57738         }
57739         me.ghostPanel.el.hide();
57740     },
57741
57742     initResizable: function(resizable) {
57743         if (this.collapsed) {
57744             resizable.disabled = true;
57745         }
57746         this.callParent([resizable]);
57747     }
57748 }, function(){
57749     this.prototype.animCollapse = Ext.enableFx;
57750 });
57751
57752 /**
57753  * Component layout for Tip/ToolTip/etc. components
57754  * @class Ext.layout.component.Tip
57755  * @extends Ext.layout.component.Dock
57756  * @private
57757  */
57758
57759 Ext.define('Ext.layout.component.Tip', {
57760
57761     /* Begin Definitions */
57762
57763     alias: ['layout.tip'],
57764
57765     extend: 'Ext.layout.component.Dock',
57766
57767     /* End Definitions */
57768
57769     type: 'tip',
57770     
57771     onLayout: function(width, height) {
57772         var me = this,
57773             owner = me.owner,
57774             el = owner.el,
57775             minWidth,
57776             maxWidth,
57777             naturalWidth,
57778             constrainedWidth,
57779             xy = el.getXY();
57780
57781         // Position offscreen so the natural width is not affected by the viewport's right edge
57782         el.setXY([-9999,-9999]);
57783
57784         // Calculate initial layout
57785         this.callParent(arguments);
57786
57787         // Handle min/maxWidth for auto-width tips
57788         if (!Ext.isNumber(width)) {
57789             minWidth = owner.minWidth;
57790             maxWidth = owner.maxWidth;
57791             // IE6/7 in strict mode have a problem doing an autoWidth
57792             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
57793                 constrainedWidth = me.doAutoWidth();
57794             } else {
57795                 naturalWidth = el.getWidth();
57796             }
57797             if (naturalWidth < minWidth) {
57798                 constrainedWidth = minWidth;
57799             }
57800             else if (naturalWidth > maxWidth) {
57801                 constrainedWidth = maxWidth;
57802             }
57803             if (constrainedWidth) {
57804                 this.callParent([constrainedWidth, height]);
57805             }
57806         }
57807
57808         // Restore position
57809         el.setXY(xy);
57810     },
57811     
57812     doAutoWidth: function(){
57813         var me = this,
57814             owner = me.owner,
57815             body = owner.body,
57816             width = body.getTextWidth();
57817             
57818         if (owner.header) {
57819             width = Math.max(width, owner.header.getWidth());
57820         }
57821         if (!Ext.isDefined(me.frameWidth)) {
57822             me.frameWidth = owner.el.getWidth() - body.getWidth();
57823         }
57824         width += me.frameWidth + body.getPadding('lr');
57825         return width;
57826     }
57827 });
57828
57829 /**
57830  * @class Ext.tip.Tip
57831  * @extends Ext.panel.Panel
57832  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
57833  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
57834  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
57835  * @xtype tip
57836  */
57837 Ext.define('Ext.tip.Tip', {
57838     extend: 'Ext.panel.Panel',
57839     requires: [ 'Ext.layout.component.Tip' ],
57840     alternateClassName: 'Ext.Tip',
57841     /**
57842      * @cfg {Boolean} [closable=false]
57843      * True to render a close tool button into the tooltip header.
57844      */
57845     /**
57846      * @cfg {Number} width
57847      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
57848      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
57849      */
57850     /**
57851      * @cfg {Number} minWidth The minimum width of the tip in pixels.
57852      */
57853     minWidth : 40,
57854     /**
57855      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
57856      */
57857     maxWidth : 300,
57858     /**
57859      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
57860      * for bottom-right shadow.
57861      */
57862     shadow : "sides",
57863
57864     /**
57865      * @cfg {String} defaultAlign
57866      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
57867      * to its element of origin.
57868      */
57869     defaultAlign : "tl-bl?",
57870     /**
57871      * @cfg {Boolean} constrainPosition
57872      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
57873      */
57874     constrainPosition : true,
57875
57876     // @inherited
57877     frame: false,
57878
57879     // private panel overrides
57880     autoRender: true,
57881     hidden: true,
57882     baseCls: Ext.baseCSSPrefix + 'tip',
57883     floating: {
57884         shadow: true,
57885         shim: true,
57886         constrain: true
57887     },
57888     focusOnToFront: false,
57889     componentLayout: 'tip',
57890
57891     /**
57892      * @cfg {String} closeAction
57893      * <p>The action to take when the close header tool is clicked:
57894      * <div class="mdetail-params"><ul>
57895      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
57896      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
57897      * it and all descendant Components. The window will <b>not</b> be available to be
57898      * redisplayed via the {@link #show} method.
57899      * </div></li>
57900      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
57901      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
57902      * The window will be available to be redisplayed via the {@link #show} method.
57903      * </div></li>
57904      * </ul></div>
57905      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
57906      * which will invoke the approriate closeAction.
57907      */
57908     closeAction: 'hide',
57909
57910     ariaRole: 'tooltip',
57911
57912     initComponent: function() {
57913         var me = this;
57914
57915         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
57916         me.callParent(arguments);
57917
57918         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
57919         me.constrain = me.constrain || me.constrainPosition;
57920     },
57921
57922     /**
57923      * Shows this tip at the specified XY position.  Example usage:
57924      * <pre><code>
57925 // Show the tip at x:50 and y:100
57926 tip.showAt([50,100]);
57927 </code></pre>
57928      * @param {Number[]} xy An array containing the x and y coordinates
57929      */
57930     showAt : function(xy){
57931         var me = this;
57932         this.callParent(arguments);
57933         // Show may have been vetoed.
57934         if (me.isVisible()) {
57935             me.setPagePosition(xy[0], xy[1]);
57936             if (me.constrainPosition || me.constrain) {
57937                 me.doConstrain();
57938             }
57939             me.toFront(true);
57940         }
57941     },
57942
57943     /**
57944      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
57945      * anchor position value.  Example usage:
57946      * <pre><code>
57947 // Show the tip at the default position ('tl-br?')
57948 tip.showBy('my-el');
57949
57950 // Show the tip's top-left corner anchored to the element's top-right corner
57951 tip.showBy('my-el', 'tl-tr');
57952 </code></pre>
57953      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
57954      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
57955      * {@link #defaultAlign} if specified).
57956      */
57957     showBy : function(el, pos) {
57958         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
57959     },
57960
57961     /**
57962      * @private
57963      * @override
57964      * Set Tip draggable using base Component's draggability
57965      */
57966     initDraggable : function(){
57967         var me = this;
57968         me.draggable = {
57969             el: me.getDragEl(),
57970             delegate: me.header.el,
57971             constrain: me,
57972             constrainTo: me.el.getScopeParent()
57973         };
57974         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
57975         Ext.Component.prototype.initDraggable.call(me);
57976     },
57977
57978     // Tip does not ghost. Drag is "live"
57979     ghost: undefined,
57980     unghost: undefined
57981 });
57982
57983 /**
57984  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
57985  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
57986  * control over the tooltip's alignment relative to the target element or mouse, and the timing
57987  * of when it is automatically shown and hidden.
57988  *
57989  * This implementation does **not** have a built-in method of automatically populating the tooltip's
57990  * text based on the target element; you must either configure a fixed {@link #html} value for each
57991  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
57992  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
57993  * convenient way of automatically populating and configuring a tooltip based on specific DOM
57994  * attributes of each target element.
57995  *
57996  * # Basic Example
57997  *
57998  *     var tip = Ext.create('Ext.tip.ToolTip', {
57999  *         target: 'clearButton',
58000  *         html: 'Press this button to clear the form'
58001  *     });
58002  *
58003  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
58004  *
58005  * # Delegation
58006  *
58007  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
58008  * one ToolTip to many elements under a common parent. This is more efficient than creating many
58009  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
58010  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
58011  * appropriate sub-elements.
58012  *
58013  * When using delegation, it is likely that you will want to programmatically change the content
58014  * of the ToolTip based on each delegate element; you can do this by implementing a custom
58015  * listener for the {@link #beforeshow} event. Example:
58016  *
58017  *     var store = Ext.create('Ext.data.ArrayStore', {
58018  *         fields: ['company', 'price', 'change'],
58019  *         data: [
58020  *             ['3m Co',                               71.72, 0.02],
58021  *             ['Alcoa Inc',                           29.01, 0.42],
58022  *             ['Altria Group Inc',                    83.81, 0.28],
58023  *             ['American Express Company',            52.55, 0.01],
58024  *             ['American International Group, Inc.',  64.13, 0.31],
58025  *             ['AT&T Inc.',                           31.61, -0.48]
58026  *         ]
58027  *     });
58028  *
58029  *     var grid = Ext.create('Ext.grid.Panel', {
58030  *         title: 'Array Grid',
58031  *         store: store,
58032  *         columns: [
58033  *             {text: 'Company', flex: 1, dataIndex: 'company'},
58034  *             {text: 'Price', width: 75, dataIndex: 'price'},
58035  *             {text: 'Change', width: 75, dataIndex: 'change'}
58036  *         ],
58037  *         height: 200,
58038  *         width: 400,
58039  *         renderTo: Ext.getBody()
58040  *     });
58041  *
58042  *     grid.getView().on('render', function(view) {
58043  *         view.tip = Ext.create('Ext.tip.ToolTip', {
58044  *             // The overall target element.
58045  *             target: view.el,
58046  *             // Each grid row causes its own seperate show and hide.
58047  *             delegate: view.itemSelector,
58048  *             // Moving within the row should not hide the tip.
58049  *             trackMouse: true,
58050  *             // Render immediately so that tip.body can be referenced prior to the first show.
58051  *             renderTo: Ext.getBody(),
58052  *             listeners: {
58053  *                 // Change content dynamically depending on which element triggered the show.
58054  *                 beforeshow: function updateTipBody(tip) {
58055  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
58056  *                 }
58057  *             }
58058  *         });
58059  *     });
58060  *
58061  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
58062  *
58063  * # Alignment
58064  *
58065  * The following configuration properties allow control over how the ToolTip is aligned relative to
58066  * the target element and/or mouse pointer:
58067  *
58068  * - {@link #anchor}
58069  * - {@link #anchorToTarget}
58070  * - {@link #anchorOffset}
58071  * - {@link #trackMouse}
58072  * - {@link #mouseOffset}
58073  *
58074  * # Showing/Hiding
58075  *
58076  * The following configuration properties allow control over how and when the ToolTip is automatically
58077  * shown and hidden:
58078  *
58079  * - {@link #autoHide}
58080  * - {@link #showDelay}
58081  * - {@link #hideDelay}
58082  * - {@link #dismissDelay}
58083  *
58084  * @docauthor Jason Johnston <jason@sencha.com>
58085  */
58086 Ext.define('Ext.tip.ToolTip', {
58087     extend: 'Ext.tip.Tip',
58088     alias: 'widget.tooltip',
58089     alternateClassName: 'Ext.ToolTip',
58090     /**
58091      * @property {HTMLElement} triggerElement
58092      * When a ToolTip is configured with the `{@link #delegate}`
58093      * option to cause selected child elements of the `{@link #target}`
58094      * Element to each trigger a seperate show event, this property is set to
58095      * the DOM element which triggered the show.
58096      */
58097     /**
58098      * @cfg {HTMLElement/Ext.Element/String} target
58099      * The target element or string id to monitor for mouseover events to trigger
58100      * showing this ToolTip.
58101      */
58102     /**
58103      * @cfg {Boolean} [autoHide=true]
58104      * True to automatically hide the tooltip after the
58105      * mouse exits the target element or after the `{@link #dismissDelay}`
58106      * has expired if set.  If `{@link #closable} = true`
58107      * a close tool button will be rendered into the tooltip header.
58108      */
58109     /**
58110      * @cfg {Number} showDelay
58111      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
58112      */
58113     showDelay: 500,
58114     /**
58115      * @cfg {Number} hideDelay
58116      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
58117      * Set to 0 for the tooltip to hide immediately.
58118      */
58119     hideDelay: 200,
58120     /**
58121      * @cfg {Number} dismissDelay
58122      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
58123      * dismissDelay = 0.
58124      */
58125     dismissDelay: 5000,
58126     /**
58127      * @cfg {Number[]} [mouseOffset=[15,18]]
58128      * An XY offset from the mouse position where the tooltip should be shown.
58129      */
58130     /**
58131      * @cfg {Boolean} trackMouse
58132      * True to have the tooltip follow the mouse as it moves over the target element.
58133      */
58134     trackMouse: false,
58135     /**
58136      * @cfg {String} anchor
58137      * If specified, indicates that the tip should be anchored to a
58138      * particular side of the target element or mouse pointer ("top", "right", "bottom",
58139      * or "left"), with an arrow pointing back at the target or mouse pointer. If
58140      * {@link #constrainPosition} is enabled, this will be used as a preferred value
58141      * only and may be flipped as needed.
58142      */
58143     /**
58144      * @cfg {Boolean} anchorToTarget
58145      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
58146      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
58147      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
58148      */
58149     anchorToTarget: true,
58150     /**
58151      * @cfg {Number} anchorOffset
58152      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
58153      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
58154      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
58155      * a vertical offset.
58156      */
58157     anchorOffset: 0,
58158     /**
58159      * @cfg {String} delegate
58160      *
58161      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
58162      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
58163      * target.
58164      *
58165      * When specified, the child element of the target which caused a show event is placed into the
58166      * `{@link #triggerElement}` property before the ToolTip is shown.
58167      *
58168      * This may be useful when a Component has regular, repeating elements in it, each of which need a
58169      * ToolTip which contains information specific to that element.
58170      *
58171      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
58172      */
58173
58174     // private
58175     targetCounter: 0,
58176     quickShowInterval: 250,
58177
58178     // private
58179     initComponent: function() {
58180         var me = this;
58181         me.callParent(arguments);
58182         me.lastActive = new Date();
58183         me.setTarget(me.target);
58184         me.origAnchor = me.anchor;
58185     },
58186
58187     // private
58188     onRender: function(ct, position) {
58189         var me = this;
58190         me.callParent(arguments);
58191         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58192         me.anchorEl = me.el.createChild({
58193             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
58194         });
58195     },
58196
58197     // private
58198     afterRender: function() {
58199         var me = this,
58200             zIndex;
58201
58202         me.callParent(arguments);
58203         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
58204         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
58205     },
58206
58207     /**
58208      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
58209      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
58210      */
58211     setTarget: function(target) {
58212         var me = this,
58213             t = Ext.get(target),
58214             tg;
58215
58216         if (me.target) {
58217             tg = Ext.get(me.target);
58218             me.mun(tg, 'mouseover', me.onTargetOver, me);
58219             me.mun(tg, 'mouseout', me.onTargetOut, me);
58220             me.mun(tg, 'mousemove', me.onMouseMove, me);
58221         }
58222
58223         me.target = t;
58224         if (t) {
58225
58226             me.mon(t, {
58227                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
58228                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
58229                 freezeEvent: true,
58230
58231                 mouseover: me.onTargetOver,
58232                 mouseout: me.onTargetOut,
58233                 mousemove: me.onMouseMove,
58234                 scope: me
58235             });
58236         }
58237         if (me.anchor) {
58238             me.anchorTarget = me.target;
58239         }
58240     },
58241
58242     // private
58243     onMouseMove: function(e) {
58244         var me = this,
58245             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
58246             xy;
58247         if (t) {
58248             me.targetXY = e.getXY();
58249             if (t === me.triggerElement) {
58250                 if (!me.hidden && me.trackMouse) {
58251                     xy = me.getTargetXY();
58252                     if (me.constrainPosition) {
58253                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
58254                     }
58255                     me.setPagePosition(xy);
58256                 }
58257             } else {
58258                 me.hide();
58259                 me.lastActive = new Date(0);
58260                 me.onTargetOver(e);
58261             }
58262         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
58263             me.hide();
58264         }
58265     },
58266
58267     // private
58268     getTargetXY: function() {
58269         var me = this,
58270             mouseOffset;
58271         if (me.delegate) {
58272             me.anchorTarget = me.triggerElement;
58273         }
58274         if (me.anchor) {
58275             me.targetCounter++;
58276                 var offsets = me.getOffsets(),
58277                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
58278                     dw = Ext.Element.getViewWidth() - 5,
58279                     dh = Ext.Element.getViewHeight() - 5,
58280                     de = document.documentElement,
58281                     bd = document.body,
58282                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
58283                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
58284                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
58285                     sz = me.getSize(),
58286                     constrainPosition = me.constrainPosition;
58287
58288             me.anchorEl.removeCls(me.anchorCls);
58289
58290             if (me.targetCounter < 2 && constrainPosition) {
58291                 if (axy[0] < scrollX) {
58292                     if (me.anchorToTarget) {
58293                         me.defaultAlign = 'l-r';
58294                         if (me.mouseOffset) {
58295                             me.mouseOffset[0] *= -1;
58296                         }
58297                     }
58298                     me.anchor = 'left';
58299                     return me.getTargetXY();
58300                 }
58301                 if (axy[0] + sz.width > dw) {
58302                     if (me.anchorToTarget) {
58303                         me.defaultAlign = 'r-l';
58304                         if (me.mouseOffset) {
58305                             me.mouseOffset[0] *= -1;
58306                         }
58307                     }
58308                     me.anchor = 'right';
58309                     return me.getTargetXY();
58310                 }
58311                 if (axy[1] < scrollY) {
58312                     if (me.anchorToTarget) {
58313                         me.defaultAlign = 't-b';
58314                         if (me.mouseOffset) {
58315                             me.mouseOffset[1] *= -1;
58316                         }
58317                     }
58318                     me.anchor = 'top';
58319                     return me.getTargetXY();
58320                 }
58321                 if (axy[1] + sz.height > dh) {
58322                     if (me.anchorToTarget) {
58323                         me.defaultAlign = 'b-t';
58324                         if (me.mouseOffset) {
58325                             me.mouseOffset[1] *= -1;
58326                         }
58327                     }
58328                     me.anchor = 'bottom';
58329                     return me.getTargetXY();
58330                 }
58331             }
58332
58333             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58334             me.anchorEl.addCls(me.anchorCls);
58335             me.targetCounter = 0;
58336             return axy;
58337         } else {
58338             mouseOffset = me.getMouseOffset();
58339             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
58340         }
58341     },
58342
58343     getMouseOffset: function() {
58344         var me = this,
58345         offset = me.anchor ? [0, 0] : [15, 18];
58346         if (me.mouseOffset) {
58347             offset[0] += me.mouseOffset[0];
58348             offset[1] += me.mouseOffset[1];
58349         }
58350         return offset;
58351     },
58352
58353     // private
58354     getAnchorPosition: function() {
58355         var me = this,
58356             m;
58357         if (me.anchor) {
58358             me.tipAnchor = me.anchor.charAt(0);
58359         } else {
58360             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
58361             me.tipAnchor = m[1].charAt(0);
58362         }
58363
58364         switch (me.tipAnchor) {
58365         case 't':
58366             return 'top';
58367         case 'b':
58368             return 'bottom';
58369         case 'r':
58370             return 'right';
58371         }
58372         return 'left';
58373     },
58374
58375     // private
58376     getAnchorAlign: function() {
58377         switch (this.anchor) {
58378         case 'top':
58379             return 'tl-bl';
58380         case 'left':
58381             return 'tl-tr';
58382         case 'right':
58383             return 'tr-tl';
58384         default:
58385             return 'bl-tl';
58386         }
58387     },
58388
58389     // private
58390     getOffsets: function() {
58391         var me = this,
58392             mouseOffset,
58393             offsets,
58394             ap = me.getAnchorPosition().charAt(0);
58395         if (me.anchorToTarget && !me.trackMouse) {
58396             switch (ap) {
58397             case 't':
58398                 offsets = [0, 9];
58399                 break;
58400             case 'b':
58401                 offsets = [0, -13];
58402                 break;
58403             case 'r':
58404                 offsets = [ - 13, 0];
58405                 break;
58406             default:
58407                 offsets = [9, 0];
58408                 break;
58409             }
58410         } else {
58411             switch (ap) {
58412             case 't':
58413                 offsets = [ - 15 - me.anchorOffset, 30];
58414                 break;
58415             case 'b':
58416                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
58417                 break;
58418             case 'r':
58419                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
58420                 break;
58421             default:
58422                 offsets = [25, -13 - me.anchorOffset];
58423                 break;
58424             }
58425         }
58426         mouseOffset = me.getMouseOffset();
58427         offsets[0] += mouseOffset[0];
58428         offsets[1] += mouseOffset[1];
58429
58430         return offsets;
58431     },
58432
58433     // private
58434     onTargetOver: function(e) {
58435         var me = this,
58436             t;
58437
58438         if (me.disabled || e.within(me.target.dom, true)) {
58439             return;
58440         }
58441         t = e.getTarget(me.delegate);
58442         if (t) {
58443             me.triggerElement = t;
58444             me.clearTimer('hide');
58445             me.targetXY = e.getXY();
58446             me.delayShow();
58447         }
58448     },
58449
58450     // private
58451     delayShow: function() {
58452         var me = this;
58453         if (me.hidden && !me.showTimer) {
58454             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
58455                 me.show();
58456             } else {
58457                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
58458             }
58459         }
58460         else if (!me.hidden && me.autoHide !== false) {
58461             me.show();
58462         }
58463     },
58464
58465     // private
58466     onTargetOut: function(e) {
58467         var me = this;
58468         if (me.disabled || e.within(me.target.dom, true)) {
58469             return;
58470         }
58471         me.clearTimer('show');
58472         if (me.autoHide !== false) {
58473             me.delayHide();
58474         }
58475     },
58476
58477     // private
58478     delayHide: function() {
58479         var me = this;
58480         if (!me.hidden && !me.hideTimer) {
58481             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
58482         }
58483     },
58484
58485     /**
58486      * Hides this tooltip if visible.
58487      */
58488     hide: function() {
58489         var me = this;
58490         me.clearTimer('dismiss');
58491         me.lastActive = new Date();
58492         if (me.anchorEl) {
58493             me.anchorEl.hide();
58494         }
58495         me.callParent(arguments);
58496         delete me.triggerElement;
58497     },
58498
58499     /**
58500      * Shows this tooltip at the current event target XY position.
58501      */
58502     show: function() {
58503         var me = this;
58504
58505         // Show this Component first, so that sizing can be calculated
58506         // pre-show it off screen so that the el will have dimensions
58507         this.callParent();
58508         if (this.hidden === false) {
58509             me.setPagePosition(-10000, -10000);
58510
58511             if (me.anchor) {
58512                 me.anchor = me.origAnchor;
58513             }
58514             me.showAt(me.getTargetXY());
58515
58516             if (me.anchor) {
58517                 me.syncAnchor();
58518                 me.anchorEl.show();
58519             } else {
58520                 me.anchorEl.hide();
58521             }
58522         }
58523     },
58524
58525     // inherit docs
58526     showAt: function(xy) {
58527         var me = this;
58528         me.lastActive = new Date();
58529         me.clearTimers();
58530
58531         // Only call if this is hidden. May have been called from show above.
58532         if (!me.isVisible()) {
58533             this.callParent(arguments);
58534         }
58535
58536         // Show may have been vetoed.
58537         if (me.isVisible()) {
58538             me.setPagePosition(xy[0], xy[1]);
58539             if (me.constrainPosition || me.constrain) {
58540                 me.doConstrain();
58541             }
58542             me.toFront(true);
58543         }
58544
58545         if (me.dismissDelay && me.autoHide !== false) {
58546             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
58547         }
58548         if (me.anchor) {
58549             me.syncAnchor();
58550             if (!me.anchorEl.isVisible()) {
58551                 me.anchorEl.show();
58552             }
58553         } else {
58554             me.anchorEl.hide();
58555         }
58556     },
58557
58558     // private
58559     syncAnchor: function() {
58560         var me = this,
58561             anchorPos,
58562             targetPos,
58563             offset;
58564         switch (me.tipAnchor.charAt(0)) {
58565         case 't':
58566             anchorPos = 'b';
58567             targetPos = 'tl';
58568             offset = [20 + me.anchorOffset, 1];
58569             break;
58570         case 'r':
58571             anchorPos = 'l';
58572             targetPos = 'tr';
58573             offset = [ - 1, 12 + me.anchorOffset];
58574             break;
58575         case 'b':
58576             anchorPos = 't';
58577             targetPos = 'bl';
58578             offset = [20 + me.anchorOffset, -1];
58579             break;
58580         default:
58581             anchorPos = 'r';
58582             targetPos = 'tl';
58583             offset = [1, 12 + me.anchorOffset];
58584             break;
58585         }
58586         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
58587     },
58588
58589     // private
58590     setPagePosition: function(x, y) {
58591         var me = this;
58592         me.callParent(arguments);
58593         if (me.anchor) {
58594             me.syncAnchor();
58595         }
58596     },
58597
58598     // private
58599     clearTimer: function(name) {
58600         name = name + 'Timer';
58601         clearTimeout(this[name]);
58602         delete this[name];
58603     },
58604
58605     // private
58606     clearTimers: function() {
58607         var me = this;
58608         me.clearTimer('show');
58609         me.clearTimer('dismiss');
58610         me.clearTimer('hide');
58611     },
58612
58613     // private
58614     onShow: function() {
58615         var me = this;
58616         me.callParent();
58617         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58618     },
58619
58620     // private
58621     onHide: function() {
58622         var me = this;
58623         me.callParent();
58624         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58625     },
58626
58627     // private
58628     onDocMouseDown: function(e) {
58629         var me = this;
58630         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
58631             me.disable();
58632             Ext.defer(me.doEnable, 100, me);
58633         }
58634     },
58635
58636     // private
58637     doEnable: function() {
58638         if (!this.isDestroyed) {
58639             this.enable();
58640         }
58641     },
58642
58643     // private
58644     onDisable: function() {
58645         this.callParent();
58646         this.clearTimers();
58647         this.hide();
58648     },
58649
58650     beforeDestroy: function() {
58651         var me = this;
58652         me.clearTimers();
58653         Ext.destroy(me.anchorEl);
58654         delete me.anchorEl;
58655         delete me.target;
58656         delete me.anchorTarget;
58657         delete me.triggerElement;
58658         me.callParent();
58659     },
58660
58661     // private
58662     onDestroy: function() {
58663         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
58664         this.callParent();
58665     }
58666 });
58667
58668 /**
58669  * @class Ext.tip.QuickTip
58670  * @extends Ext.tip.ToolTip
58671  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
58672  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
58673  * @xtype quicktip
58674  */
58675 Ext.define('Ext.tip.QuickTip', {
58676     extend: 'Ext.tip.ToolTip',
58677     alternateClassName: 'Ext.QuickTip',
58678     /**
58679      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
58680      */
58681     /**
58682      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
58683      */
58684     interceptTitles : false,
58685
58686     // Force creation of header Component
58687     title: '&#160;',
58688
58689     // private
58690     tagConfig : {
58691         namespace : "data-",
58692         attribute : "qtip",
58693         width : "qwidth",
58694         target : "target",
58695         title : "qtitle",
58696         hide : "hide",
58697         cls : "qclass",
58698         align : "qalign",
58699         anchor : "anchor"
58700     },
58701
58702     // private
58703     initComponent : function(){
58704         var me = this;
58705
58706         me.target = me.target || Ext.getDoc();
58707         me.targets = me.targets || {};
58708         me.callParent();
58709     },
58710
58711     /**
58712      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
58713      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
58714      * <div class="mdetail-params"><ul>
58715      * <li>autoHide</li>
58716      * <li>cls</li>
58717      * <li>dismissDelay (overrides the singleton value)</li>
58718      * <li>target (required)</li>
58719      * <li>text (required)</li>
58720      * <li>title</li>
58721      * <li>width</li></ul></div>
58722      * @param {Object} config The config object
58723      */
58724     register : function(config){
58725         var configs = Ext.isArray(config) ? config : arguments,
58726             i = 0,
58727             len = configs.length,
58728             target, j, targetLen;
58729
58730         for (; i < len; i++) {
58731             config = configs[i];
58732             target = config.target;
58733             if (target) {
58734                 if (Ext.isArray(target)) {
58735                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
58736                         this.targets[Ext.id(target[j])] = config;
58737                     }
58738                 } else{
58739                     this.targets[Ext.id(target)] = config;
58740                 }
58741             }
58742         }
58743     },
58744
58745     /**
58746      * Removes this quick tip from its element and destroys it.
58747      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
58748      */
58749     unregister : function(el){
58750         delete this.targets[Ext.id(el)];
58751     },
58752
58753     /**
58754      * Hides a visible tip or cancels an impending show for a particular element.
58755      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
58756      */
58757     cancelShow: function(el){
58758         var me = this,
58759             activeTarget = me.activeTarget;
58760
58761         el = Ext.get(el).dom;
58762         if (me.isVisible()) {
58763             if (activeTarget && activeTarget.el == el) {
58764                 me.hide();
58765             }
58766         } else if (activeTarget && activeTarget.el == el) {
58767             me.clearTimer('show');
58768         }
58769     },
58770
58771     /**
58772      * @private
58773      * Reads the tip text from the closest node to the event target which contains the attribute we
58774      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
58775      * which the text was read.
58776      */
58777     getTipCfg: function(e) {
58778         var t = e.getTarget(),
58779             titleText = t.title,
58780             cfg;
58781
58782         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
58783             t.qtip = titleText;
58784             t.removeAttribute("title");
58785             e.preventDefault();
58786             return {
58787                 text: titleText
58788             };
58789         }
58790         else {
58791             cfg = this.tagConfig;
58792             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
58793             if (t) {
58794                 return {
58795                     target: t,
58796                     text: t.getAttribute(cfg.namespace + cfg.attribute)
58797                 };
58798             }
58799         }
58800     },
58801
58802     // private
58803     onTargetOver : function(e){
58804         var me = this,
58805             target = e.getTarget(),
58806             elTarget,
58807             cfg,
58808             ns,
58809             tipConfig,
58810             autoHide;
58811
58812         if (me.disabled) {
58813             return;
58814         }
58815
58816         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
58817         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
58818         // that smashed Ext.EventObject.
58819         me.targetXY = e.getXY();
58820
58821         if(!target || target.nodeType !== 1 || target == document || target == document.body){
58822             return;
58823         }
58824
58825         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
58826             me.clearTimer('hide');
58827             me.show();
58828             return;
58829         }
58830
58831         if (target) {
58832             Ext.Object.each(me.targets, function(key, value) {
58833                 var targetEl = Ext.fly(value.target);
58834                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
58835                     elTarget = targetEl.dom;
58836                     return false;
58837                 }
58838             });
58839             if (elTarget) {
58840                 me.activeTarget = me.targets[elTarget.id];
58841                 me.activeTarget.el = target;
58842                 me.anchor = me.activeTarget.anchor;
58843                 if (me.anchor) {
58844                     me.anchorTarget = target;
58845                 }
58846                 me.delayShow();
58847                 return;
58848             }
58849         }
58850
58851         elTarget = Ext.get(target);
58852         cfg = me.tagConfig;
58853         ns = cfg.namespace;
58854         tipConfig = me.getTipCfg(e);
58855
58856         if (tipConfig) {
58857
58858             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
58859             // Change our target element to match that from which the tip text attribute was read.
58860             if (tipConfig.target) {
58861                 target = tipConfig.target;
58862                 elTarget = Ext.get(target);
58863             }
58864             autoHide = elTarget.getAttribute(ns + cfg.hide);
58865
58866             me.activeTarget = {
58867                 el: target,
58868                 text: tipConfig.text,
58869                 width: +elTarget.getAttribute(ns + cfg.width) || null,
58870                 autoHide: autoHide != "user" && autoHide !== 'false',
58871                 title: elTarget.getAttribute(ns + cfg.title),
58872                 cls: elTarget.getAttribute(ns + cfg.cls),
58873                 align: elTarget.getAttribute(ns + cfg.align)
58874
58875             };
58876             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
58877             if (me.anchor) {
58878                 me.anchorTarget = target;
58879             }
58880             me.delayShow();
58881         }
58882     },
58883
58884     // private
58885     onTargetOut : function(e){
58886         var me = this;
58887
58888         // If moving within the current target, and it does not have a new tip, ignore the mouseout
58889         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
58890             return;
58891         }
58892
58893         me.clearTimer('show');
58894         if (me.autoHide !== false) {
58895             me.delayHide();
58896         }
58897     },
58898
58899     // inherit docs
58900     showAt : function(xy){
58901         var me = this,
58902             target = me.activeTarget;
58903
58904         if (target) {
58905             if (!me.rendered) {
58906                 me.render(Ext.getBody());
58907                 me.activeTarget = target;
58908             }
58909             if (target.title) {
58910                 me.setTitle(target.title || '');
58911                 me.header.show();
58912             } else {
58913                 me.header.hide();
58914             }
58915             me.body.update(target.text);
58916             me.autoHide = target.autoHide;
58917             me.dismissDelay = target.dismissDelay || me.dismissDelay;
58918             if (me.lastCls) {
58919                 me.el.removeCls(me.lastCls);
58920                 delete me.lastCls;
58921             }
58922             if (target.cls) {
58923                 me.el.addCls(target.cls);
58924                 me.lastCls = target.cls;
58925             }
58926
58927             me.setWidth(target.width);
58928
58929             if (me.anchor) {
58930                 me.constrainPosition = false;
58931             } else if (target.align) { // TODO: this doesn't seem to work consistently
58932                 xy = me.el.getAlignToXY(target.el, target.align);
58933                 me.constrainPosition = false;
58934             }else{
58935                 me.constrainPosition = true;
58936             }
58937         }
58938         me.callParent([xy]);
58939     },
58940
58941     // inherit docs
58942     hide: function(){
58943         delete this.activeTarget;
58944         this.callParent();
58945     }
58946 });
58947
58948 /**
58949  * @class Ext.tip.QuickTipManager
58950  *
58951  * Provides attractive and customizable tooltips for any element. The QuickTips
58952  * singleton is used to configure and manage tooltips globally for multiple elements
58953  * in a generic manner.  To create individual tooltips with maximum customizability,
58954  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
58955  *
58956  * Quicktips can be configured via tag attributes directly in markup, or by
58957  * registering quick tips programmatically via the {@link #register} method.
58958  *
58959  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
58960  * {@link #getQuickTip}, and supports all the methods, and all the all the
58961  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
58962  * tooltips shown by the singleton.
58963  *
58964  * Below is the summary of the configuration properties which can be used.
58965  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
58966  *
58967  * ## QuickTips singleton configs (all are optional)
58968  *
58969  *  - `dismissDelay`
58970  *  - `hideDelay`
58971  *  - `maxWidth`
58972  *  - `minWidth`
58973  *  - `showDelay`
58974  *  - `trackMouse`
58975  *
58976  * ## Target element configs (optional unless otherwise noted)
58977  *
58978  *  - `autoHide`
58979  *  - `cls`
58980  *  - `dismissDelay` (overrides singleton value)
58981  *  - `target` (required)
58982  *  - `text` (required)
58983  *  - `title`
58984  *  - `width`
58985  *
58986  * Here is an example showing how some of these config options could be used:
58987  *
58988  *     @example
58989  *     // Init the singleton.  Any tag-based quick tips will start working.
58990  *     Ext.tip.QuickTipManager.init();
58991  *
58992  *     // Apply a set of config properties to the singleton
58993  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
58994  *         maxWidth: 200,
58995  *         minWidth: 100,
58996  *         showDelay: 50      // Show 50ms after entering target
58997  *     });
58998  *
58999  *     // Create a small panel to add a quick tip to
59000  *     Ext.create('Ext.container.Container', {
59001  *         id: 'quickTipContainer',
59002  *         width: 200,
59003  *         height: 150,
59004  *         style: {
59005  *             backgroundColor:'#000000'
59006  *         },
59007  *         renderTo: Ext.getBody()
59008  *     });
59009  *
59010  *
59011  *     // Manually register a quick tip for a specific element
59012  *     Ext.tip.QuickTipManager.register({
59013  *         target: 'quickTipContainer',
59014  *         title: 'My Tooltip',
59015  *         text: 'This tooltip was added in code',
59016  *         width: 100,
59017  *         dismissDelay: 10000 // Hide after 10 seconds hover
59018  *     });
59019  *
59020  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
59021  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
59022  * of supported attributes (optional unless otherwise noted):
59023  *
59024  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
59025  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
59026  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
59027  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
59028  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
59029  *
59030  * Here is an example of configuring an HTML element to display a tooltip from markup:
59031  *
59032  *     // Add a quick tip to an HTML button
59033  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
59034  *          data-qtip="This is a quick tip from markup!"></input>
59035  *
59036  * @singleton
59037  */
59038 Ext.define('Ext.tip.QuickTipManager', function() {
59039     var tip,
59040         disabled = false;
59041
59042     return {
59043         requires: ['Ext.tip.QuickTip'],
59044         singleton: true,
59045         alternateClassName: 'Ext.QuickTips',
59046
59047         /**
59048          * Initialize the global QuickTips instance and prepare any quick tips.
59049          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
59050          * preload images. (Defaults to true)
59051          * @param {Object} config (optional) config object for the created QuickTip. By
59052          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
59053          * be changed by supplying an xtype property or a className property in this object.
59054          * All other properties on this object are configuration for the created component.
59055          */
59056         init : function (autoRender, config) {
59057             if (!tip) {
59058                 if (!Ext.isReady) {
59059                     Ext.onReady(function(){
59060                         Ext.tip.QuickTipManager.init(autoRender);
59061                     });
59062                     return;
59063                 }
59064
59065                 var tipConfig = Ext.apply({ disabled: disabled }, config),
59066                     className = tipConfig.className,
59067                     xtype = tipConfig.xtype;
59068
59069                 if (className) {
59070                     delete tipConfig.className;
59071                 } else if (xtype) {
59072                     className = 'widget.' + xtype;
59073                     delete tipConfig.xtype;
59074                 }
59075
59076                 if (autoRender !== false) {
59077                     tipConfig.renderTo = document.body;
59078
59079                 }
59080
59081                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
59082             }
59083         },
59084
59085         /**
59086          * Destroy the QuickTips instance.
59087          */
59088         destroy: function() {
59089             if (tip) {
59090                 var undef;
59091                 tip.destroy();
59092                 tip = undef;
59093             }
59094         },
59095
59096         // Protected method called by the dd classes
59097         ddDisable : function(){
59098             // don't disable it if we don't need to
59099             if(tip && !disabled){
59100                 tip.disable();
59101             }
59102         },
59103
59104         // Protected method called by the dd classes
59105         ddEnable : function(){
59106             // only enable it if it hasn't been disabled
59107             if(tip && !disabled){
59108                 tip.enable();
59109             }
59110         },
59111
59112         /**
59113          * Enable quick tips globally.
59114          */
59115         enable : function(){
59116             if(tip){
59117                 tip.enable();
59118             }
59119             disabled = false;
59120         },
59121
59122         /**
59123          * Disable quick tips globally.
59124          */
59125         disable : function(){
59126             if(tip){
59127                 tip.disable();
59128             }
59129             disabled = true;
59130         },
59131
59132         /**
59133          * Returns true if quick tips are enabled, else false.
59134          * @return {Boolean}
59135          */
59136         isEnabled : function(){
59137             return tip !== undefined && !tip.disabled;
59138         },
59139
59140         /**
59141          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
59142          * @return {Ext.tip.QuickTip}
59143          */
59144         getQuickTip : function(){
59145             return tip;
59146         },
59147
59148         /**
59149          * Configures a new quick tip instance and assigns it to a target element.  See
59150          * {@link Ext.tip.QuickTip#register} for details.
59151          * @param {Object} config The config object
59152          */
59153         register : function(){
59154             tip.register.apply(tip, arguments);
59155         },
59156
59157         /**
59158          * Removes any registered quick tip from the target element and destroys it.
59159          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59160          */
59161         unregister : function(){
59162             tip.unregister.apply(tip, arguments);
59163         },
59164
59165         /**
59166          * Alias of {@link #register}.
59167          * @param {Object} config The config object
59168          */
59169         tips : function(){
59170             tip.register.apply(tip, arguments);
59171         }
59172     };
59173 }());
59174 /**
59175  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
59176  * A typical Ext.app.Application might look like this:
59177  *
59178  *     Ext.application({
59179  *         name: 'MyApp',
59180  *         launch: function() {
59181  *             Ext.create('Ext.container.Viewport', {
59182  *                 items: {
59183  *                     html: 'My App'
59184  *                 }
59185  *             });
59186  *         }
59187  *     });
59188  *
59189  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
59190  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
59191  * of colliding global variables.
59192  *
59193  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
59194  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
59195  * the example above.
59196  *
59197  * # Telling Application about the rest of the app
59198  *
59199  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
59200  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
59201  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
59202  * Here's how we'd tell our Application about all these things:
59203  *
59204  *     Ext.application({
59205  *         name: 'Blog',
59206  *         models: ['Post', 'Comment'],
59207  *         controllers: ['Posts', 'Comments'],
59208  *
59209  *         launch: function() {
59210  *             ...
59211  *         }
59212  *     });
59213  *
59214  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
59215  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
59216  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
59217  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
59218  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
59219  * automatically loaded. Here's how our Posts controller like be defined:
59220  *
59221  *     Ext.define('MyApp.controller.Posts', {
59222  *         extend: 'Ext.app.Controller',
59223  *         views: ['posts.List', 'posts.Edit'],
59224  *
59225  *         //the rest of the Controller here
59226  *     });
59227  *
59228  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
59229  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
59230  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
59231  * application using the Ext JS 4 SDK Tools.
59232  *
59233  * For more information about writing Ext JS 4 applications, please see the
59234  * [application architecture guide][mvc].
59235  *
59236  * [mvc]: #!/guide/application_architecture
59237  *
59238  * @docauthor Ed Spencer
59239  */
59240 Ext.define('Ext.app.Application', {
59241     extend: 'Ext.app.Controller',
59242
59243     requires: [
59244         'Ext.ModelManager',
59245         'Ext.data.Model',
59246         'Ext.data.StoreManager',
59247         'Ext.tip.QuickTipManager',
59248         'Ext.ComponentManager',
59249         'Ext.app.EventBus'
59250     ],
59251
59252     /**
59253      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
59254      * models and stores. Don't use spaces or special characters in the name.
59255      */
59256
59257     /**
59258      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
59259      * instance.
59260      */
59261     scope: undefined,
59262
59263     /**
59264      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
59265      */
59266     enableQuickTips: true,
59267
59268     /**
59269      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
59270      */
59271
59272     /**
59273      * @cfg {String} appFolder The path to the directory which contains all application's classes.
59274      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
59275      */
59276     appFolder: 'app',
59277
59278     /**
59279      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
59280      * before firing the launch function.
59281      */
59282     autoCreateViewport: false,
59283
59284     /**
59285      * Creates new Application.
59286      * @param {Object} [config] Config object.
59287      */
59288     constructor: function(config) {
59289         config = config || {};
59290         Ext.apply(this, config);
59291
59292         var requires = config.requires || [];
59293
59294         Ext.Loader.setPath(this.name, this.appFolder);
59295
59296         if (this.paths) {
59297             Ext.Object.each(this.paths, function(key, value) {
59298                 Ext.Loader.setPath(key, value);
59299             });
59300         }
59301
59302         this.callParent(arguments);
59303
59304         this.eventbus = Ext.create('Ext.app.EventBus');
59305
59306         var controllers = Ext.Array.from(this.controllers),
59307             ln = controllers && controllers.length,
59308             i, controller;
59309
59310         this.controllers = Ext.create('Ext.util.MixedCollection');
59311
59312         if (this.autoCreateViewport) {
59313             requires.push(this.getModuleClassName('Viewport', 'view'));
59314         }
59315
59316         for (i = 0; i < ln; i++) {
59317             requires.push(this.getModuleClassName(controllers[i], 'controller'));
59318         }
59319
59320         Ext.require(requires);
59321
59322         Ext.onReady(function() {
59323             for (i = 0; i < ln; i++) {
59324                 controller = this.getController(controllers[i]);
59325                 controller.init(this);
59326             }
59327
59328             this.onBeforeLaunch.call(this);
59329         }, this);
59330     },
59331
59332     control: function(selectors, listeners, controller) {
59333         this.eventbus.control(selectors, listeners, controller);
59334     },
59335
59336     /**
59337      * Called automatically when the page has completely loaded. This is an empty function that should be
59338      * overridden by each application that needs to take action on page load
59339      * @property launch
59340      * @type Function
59341      * @param {String} profile The detected {@link #profiles application profile}
59342      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
59343      * action immediately after running the launch function. Return false to prevent this behavior.
59344      */
59345     launch: Ext.emptyFn,
59346
59347     /**
59348      * @private
59349      */
59350     onBeforeLaunch: function() {
59351         if (this.enableQuickTips) {
59352             Ext.tip.QuickTipManager.init();
59353         }
59354
59355         if (this.autoCreateViewport) {
59356             this.getView('Viewport').create();
59357         }
59358
59359         this.launch.call(this.scope || this);
59360         this.launched = true;
59361         this.fireEvent('launch', this);
59362
59363         this.controllers.each(function(controller) {
59364             controller.onLaunch(this);
59365         }, this);
59366     },
59367
59368     getModuleClassName: function(name, type) {
59369         var namespace = Ext.Loader.getPrefix(name);
59370
59371         if (namespace.length > 0 && namespace !== name) {
59372             return name;
59373         }
59374
59375         return this.name + '.' + type + '.' + name;
59376     },
59377
59378     getController: function(name) {
59379         var controller = this.controllers.get(name);
59380
59381         if (!controller) {
59382             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
59383                 application: this,
59384                 id: name
59385             });
59386
59387             this.controllers.add(controller);
59388         }
59389
59390         return controller;
59391     },
59392
59393     getStore: function(name) {
59394         var store = Ext.StoreManager.get(name);
59395
59396         if (!store) {
59397             store = Ext.create(this.getModuleClassName(name, 'store'), {
59398                 storeId: name
59399             });
59400         }
59401
59402         return store;
59403     },
59404
59405     getModel: function(model) {
59406         model = this.getModuleClassName(model, 'model');
59407
59408         return Ext.ModelManager.getModel(model);
59409     },
59410
59411     getView: function(view) {
59412         view = this.getModuleClassName(view, 'view');
59413
59414         return Ext.ClassManager.get(view);
59415     }
59416 });
59417
59418 /**
59419  * @class Ext.chart.Callout
59420  * A mixin providing callout functionality for Ext.chart.series.Series.
59421  */
59422 Ext.define('Ext.chart.Callout', {
59423
59424     /* Begin Definitions */
59425
59426     /* End Definitions */
59427
59428     constructor: function(config) {
59429         if (config.callouts) {
59430             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
59431                 color: "#000",
59432                 font: "11px Helvetica, sans-serif"
59433             });
59434             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
59435             this.calloutsArray = [];
59436         }
59437     },
59438
59439     renderCallouts: function() {
59440         if (!this.callouts) {
59441             return;
59442         }
59443
59444         var me = this,
59445             items = me.items,
59446             animate = me.chart.animate,
59447             config = me.callouts,
59448             styles = config.styles,
59449             group = me.calloutsArray,
59450             store = me.chart.store,
59451             len = store.getCount(),
59452             ratio = items.length / len,
59453             previouslyPlacedCallouts = [],
59454             i,
59455             count,
59456             j,
59457             p;
59458             
59459         for (i = 0, count = 0; i < len; i++) {
59460             for (j = 0; j < ratio; j++) {
59461                 var item = items[count],
59462                     label = group[count],
59463                     storeItem = store.getAt(i),
59464                     display;
59465                 
59466                 display = config.filter(storeItem);
59467                 
59468                 if (!display && !label) {
59469                     count++;
59470                     continue;               
59471                 }
59472                 
59473                 if (!label) {
59474                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
59475                 }
59476                 for (p in label) {
59477                     if (label[p] && label[p].setAttributes) {
59478                         label[p].setAttributes(styles, true);
59479                     }
59480                 }
59481                 if (!display) {
59482                     for (p in label) {
59483                         if (label[p]) {
59484                             if (label[p].setAttributes) {
59485                                 label[p].setAttributes({
59486                                     hidden: true
59487                                 }, true);
59488                             } else if(label[p].setVisible) {
59489                                 label[p].setVisible(false);
59490                             }
59491                         }
59492                     }
59493                 }
59494                 config.renderer(label, storeItem);
59495                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
59496                                   j, count, previouslyPlacedCallouts);
59497                 previouslyPlacedCallouts.push(label);
59498                 count++;
59499             }
59500         }
59501         this.hideCallouts(count);
59502     },
59503
59504     onCreateCallout: function(storeItem, item, i, display) {
59505         var me = this,
59506             group = me.calloutsGroup,
59507             config = me.callouts,
59508             styles = config.styles,
59509             width = styles.width,
59510             height = styles.height,
59511             chart = me.chart,
59512             surface = chart.surface,
59513             calloutObj = {
59514                 //label: false,
59515                 //box: false,
59516                 lines: false
59517             };
59518
59519         calloutObj.lines = surface.add(Ext.apply({},
59520         {
59521             type: 'path',
59522             path: 'M0,0',
59523             stroke: me.getLegendColor() || '#555'
59524         },
59525         styles));
59526
59527         if (config.items) {
59528             calloutObj.panel = Ext.create('widget.panel', {
59529                 style: "position: absolute;",    
59530                 width: width,
59531                 height: height,
59532                 items: config.items,
59533                 renderTo: chart.el
59534             });
59535         }
59536
59537         return calloutObj;
59538     },
59539
59540     hideCallouts: function(index) {
59541         var calloutsArray = this.calloutsArray,
59542             len = calloutsArray.length,
59543             co,
59544             p;
59545         while (len-->index) {
59546             co = calloutsArray[len];
59547             for (p in co) {
59548                 if (co[p]) {
59549                     co[p].hide(true);
59550                 }
59551             }
59552         }
59553     }
59554 });
59555
59556 /**
59557  * @class Ext.draw.CompositeSprite
59558  * @extends Ext.util.MixedCollection
59559  *
59560  * A composite Sprite handles a group of sprites with common methods to a sprite
59561  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
59562  * added to the group.
59563  *
59564  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
59565  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
59566  *
59567  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
59568  * rendered:
59569  *
59570  *     var group = Ext.create('Ext.draw.CompositeSprite', {
59571  *         surface: drawComponent.surface
59572  *     });
59573  *                  
59574  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
59575  *  
59576  *     group.add(sprite1);
59577  *     group.add(sprite2);
59578  *     group.add(sprite3);
59579  *                  
59580  * And then apply common Sprite methods to them:
59581  *  
59582  *     group.setAttributes({
59583  *         fill: '#f00'
59584  *     }, true);
59585  */
59586 Ext.define('Ext.draw.CompositeSprite', {
59587
59588     /* Begin Definitions */
59589
59590     extend: 'Ext.util.MixedCollection',
59591     mixins: {
59592         animate: 'Ext.util.Animate'
59593     },
59594
59595     /* End Definitions */
59596     isCompositeSprite: true,
59597     constructor: function(config) {
59598         var me = this;
59599         
59600         config = config || {};
59601         Ext.apply(me, config);
59602
59603         me.addEvents(
59604             'mousedown',
59605             'mouseup',
59606             'mouseover',
59607             'mouseout',
59608             'click'
59609         );
59610         me.id = Ext.id(null, 'ext-sprite-group-');
59611         me.callParent();
59612     },
59613
59614     // @private
59615     onClick: function(e) {
59616         this.fireEvent('click', e);
59617     },
59618
59619     // @private
59620     onMouseUp: function(e) {
59621         this.fireEvent('mouseup', e);
59622     },
59623
59624     // @private
59625     onMouseDown: function(e) {
59626         this.fireEvent('mousedown', e);
59627     },
59628
59629     // @private
59630     onMouseOver: function(e) {
59631         this.fireEvent('mouseover', e);
59632     },
59633
59634     // @private
59635     onMouseOut: function(e) {
59636         this.fireEvent('mouseout', e);
59637     },
59638
59639     attachEvents: function(o) {
59640         var me = this;
59641         
59642         o.on({
59643             scope: me,
59644             mousedown: me.onMouseDown,
59645             mouseup: me.onMouseUp,
59646             mouseover: me.onMouseOver,
59647             mouseout: me.onMouseOut,
59648             click: me.onClick
59649         });
59650     },
59651
59652     // Inherit docs from MixedCollection
59653     add: function(key, o) {
59654         var result = this.callParent(arguments);
59655         this.attachEvents(result);
59656         return result;
59657     },
59658
59659     insert: function(index, key, o) {
59660         return this.callParent(arguments);
59661     },
59662
59663     // Inherit docs from MixedCollection
59664     remove: function(o) {
59665         var me = this;
59666         
59667         o.un({
59668             scope: me,
59669             mousedown: me.onMouseDown,
59670             mouseup: me.onMouseUp,
59671             mouseover: me.onMouseOver,
59672             mouseout: me.onMouseOut,
59673             click: me.onClick
59674         });
59675         return me.callParent(arguments);
59676     },
59677     
59678     /**
59679      * Returns the group bounding box.
59680      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
59681      * @return {Object} an object with x, y, width, and height properties.
59682      */
59683     getBBox: function() {
59684         var i = 0,
59685             sprite,
59686             bb,
59687             items = this.items,
59688             len = this.length,
59689             infinity = Infinity,
59690             minX = infinity,
59691             maxHeight = -infinity,
59692             minY = infinity,
59693             maxWidth = -infinity,
59694             maxWidthBBox, maxHeightBBox;
59695         
59696         for (; i < len; i++) {
59697             sprite = items[i];
59698             if (sprite.el) {
59699                 bb = sprite.getBBox();
59700                 minX = Math.min(minX, bb.x);
59701                 minY = Math.min(minY, bb.y);
59702                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
59703                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
59704             }
59705         }
59706         
59707         return {
59708             x: minX,
59709             y: minY,
59710             height: maxHeight - minY,
59711             width: maxWidth - minX
59712         };
59713     },
59714
59715     /**
59716      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
59717      * provides a description of the attributes that can be set with this method.
59718      * @param {Object} attrs Attributes to be changed on the sprite.
59719      * @param {Boolean} redraw Flag to immediatly draw the change.
59720      * @return {Ext.draw.CompositeSprite} this
59721      */
59722     setAttributes: function(attrs, redraw) {
59723         var i = 0,
59724             items = this.items,
59725             len = this.length;
59726             
59727         for (; i < len; i++) {
59728             items[i].setAttributes(attrs, redraw);
59729         }
59730         return this;
59731     },
59732
59733     /**
59734      * Hides all sprites. If the first parameter of the method is true
59735      * then a redraw will be forced for each sprite.
59736      * @param {Boolean} redraw Flag to immediatly draw the change.
59737      * @return {Ext.draw.CompositeSprite} this
59738      */
59739     hide: function(redraw) {
59740         var i = 0,
59741             items = this.items,
59742             len = this.length;
59743             
59744         for (; i < len; i++) {
59745             items[i].hide(redraw);
59746         }
59747         return this;
59748     },
59749
59750     /**
59751      * Shows all sprites. If the first parameter of the method is true
59752      * then a redraw will be forced for each sprite.
59753      * @param {Boolean} redraw Flag to immediatly draw the change.
59754      * @return {Ext.draw.CompositeSprite} this
59755      */
59756     show: function(redraw) {
59757         var i = 0,
59758             items = this.items,
59759             len = this.length;
59760             
59761         for (; i < len; i++) {
59762             items[i].show(redraw);
59763         }
59764         return this;
59765     },
59766
59767     redraw: function() {
59768         var me = this,
59769             i = 0,
59770             items = me.items,
59771             surface = me.getSurface(),
59772             len = me.length;
59773         
59774         if (surface) {
59775             for (; i < len; i++) {
59776                 surface.renderItem(items[i]);
59777             }
59778         }
59779         return me;
59780     },
59781
59782     setStyle: function(obj) {
59783         var i = 0,
59784             items = this.items,
59785             len = this.length,
59786             item, el;
59787             
59788         for (; i < len; i++) {
59789             item = items[i];
59790             el = item.el;
59791             if (el) {
59792                 el.setStyle(obj);
59793             }
59794         }
59795     },
59796
59797     addCls: function(obj) {
59798         var i = 0,
59799             items = this.items,
59800             surface = this.getSurface(),
59801             len = this.length;
59802         
59803         if (surface) {
59804             for (; i < len; i++) {
59805                 surface.addCls(items[i], obj);
59806             }
59807         }
59808     },
59809
59810     removeCls: function(obj) {
59811         var i = 0,
59812             items = this.items,
59813             surface = this.getSurface(),
59814             len = this.length;
59815         
59816         if (surface) {
59817             for (; i < len; i++) {
59818                 surface.removeCls(items[i], obj);
59819             }
59820         }
59821     },
59822     
59823     /**
59824      * Grab the surface from the items
59825      * @private
59826      * @return {Ext.draw.Surface} The surface, null if not found
59827      */
59828     getSurface: function(){
59829         var first = this.first();
59830         if (first) {
59831             return first.surface;
59832         }
59833         return null;
59834     },
59835     
59836     /**
59837      * Destroys the SpriteGroup
59838      */
59839     destroy: function(){
59840         var me = this,
59841             surface = me.getSurface(),
59842             item;
59843             
59844         if (surface) {
59845             while (me.getCount() > 0) {
59846                 item = me.first();
59847                 me.remove(item);
59848                 surface.remove(item);
59849             }
59850         }
59851         me.clearListeners();
59852     }
59853 });
59854
59855 /**
59856  * @class Ext.layout.component.Auto
59857  * @extends Ext.layout.component.Component
59858  * @private
59859  *
59860  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
59861  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
59862  */
59863
59864 Ext.define('Ext.layout.component.Auto', {
59865
59866     /* Begin Definitions */
59867
59868     alias: 'layout.autocomponent',
59869
59870     extend: 'Ext.layout.component.Component',
59871
59872     /* End Definitions */
59873
59874     type: 'autocomponent',
59875
59876     onLayout : function(width, height) {
59877         this.setTargetSize(width, height);
59878     }
59879 });
59880 /**
59881  * @class Ext.chart.theme.Theme
59882  * 
59883  * Provides chart theming.
59884  * 
59885  * Used as mixins by Ext.chart.Chart.
59886  */
59887 Ext.define('Ext.chart.theme.Theme', {
59888
59889     /* Begin Definitions */
59890
59891     requires: ['Ext.draw.Color'],
59892
59893     /* End Definitions */
59894
59895     theme: 'Base',
59896     themeAttrs: false,
59897     
59898     initTheme: function(theme) {
59899         var me = this,
59900             themes = Ext.chart.theme,
59901             key, gradients;
59902         if (theme) {
59903             theme = theme.split(':');
59904             for (key in themes) {
59905                 if (key == theme[0]) {
59906                     gradients = theme[1] == 'gradients';
59907                     me.themeAttrs = new themes[key]({
59908                         useGradients: gradients
59909                     });
59910                     if (gradients) {
59911                         me.gradients = me.themeAttrs.gradients;
59912                     }
59913                     if (me.themeAttrs.background) {
59914                         me.background = me.themeAttrs.background;
59915                     }
59916                     return;
59917                 }
59918             }
59919         }
59920     }
59921 }, 
59922 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
59923 function() {
59924    /* Theme constructor: takes either a complex object with styles like:
59925   
59926    {
59927         axis: {
59928             fill: '#000',
59929             'stroke-width': 1
59930         },
59931         axisLabelTop: {
59932             fill: '#000',
59933             font: '11px Arial'
59934         },
59935         axisLabelLeft: {
59936             fill: '#000',
59937             font: '11px Arial'
59938         },
59939         axisLabelRight: {
59940             fill: '#000',
59941             font: '11px Arial'
59942         },
59943         axisLabelBottom: {
59944             fill: '#000',
59945             font: '11px Arial'
59946         },
59947         axisTitleTop: {
59948             fill: '#000',
59949             font: '11px Arial'
59950         },
59951         axisTitleLeft: {
59952             fill: '#000',
59953             font: '11px Arial'
59954         },
59955         axisTitleRight: {
59956             fill: '#000',
59957             font: '11px Arial'
59958         },
59959         axisTitleBottom: {
59960             fill: '#000',
59961             font: '11px Arial'
59962         },
59963         series: {
59964             'stroke-width': 1
59965         },
59966         seriesLabel: {
59967             font: '12px Arial',
59968             fill: '#333'
59969         },
59970         marker: {
59971             stroke: '#555',
59972             fill: '#000',
59973             radius: 3,
59974             size: 3
59975         },
59976         seriesThemes: [{
59977             fill: '#C6DBEF'
59978         }, {
59979             fill: '#9ECAE1'
59980         }, {
59981             fill: '#6BAED6'
59982         }, {
59983             fill: '#4292C6'
59984         }, {
59985             fill: '#2171B5'
59986         }, {
59987             fill: '#084594'
59988         }],
59989         markerThemes: [{
59990             fill: '#084594',
59991             type: 'circle' 
59992         }, {
59993             fill: '#2171B5',
59994             type: 'cross'
59995         }, {
59996             fill: '#4292C6',
59997             type: 'plus'
59998         }]
59999     }
60000   
60001   ...or also takes just an array of colors and creates the complex object:
60002   
60003   {
60004       colors: ['#aaa', '#bcd', '#eee']
60005   }
60006   
60007   ...or takes just a base color and makes a theme from it
60008   
60009   {
60010       baseColor: '#bce'
60011   }
60012   
60013   To create a new theme you may add it to the Themes object:
60014   
60015   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
60016       constructor: function(config) {
60017           Ext.chart.theme.call(this, config, {
60018               baseColor: '#mybasecolor'
60019           });
60020       }
60021   });
60022   
60023   //Proposal:
60024   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
60025   
60026   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
60027   
60028   {
60029       theme: 'mynewtheme'
60030   }
60031  */
60032
60033 (function() {
60034     Ext.chart.theme = function(config, base) {
60035         config = config || {};
60036         var i = 0, l, colors, color,
60037             seriesThemes, markerThemes,
60038             seriesTheme, markerTheme, 
60039             key, gradients = [],
60040             midColor, midL;
60041         
60042         if (config.baseColor) {
60043             midColor = Ext.draw.Color.fromString(config.baseColor);
60044             midL = midColor.getHSL()[2];
60045             if (midL < 0.15) {
60046                 midColor = midColor.getLighter(0.3);
60047             } else if (midL < 0.3) {
60048                 midColor = midColor.getLighter(0.15);
60049             } else if (midL > 0.85) {
60050                 midColor = midColor.getDarker(0.3);
60051             } else if (midL > 0.7) {
60052                 midColor = midColor.getDarker(0.15);
60053             }
60054             config.colors = [ midColor.getDarker(0.3).toString(),
60055                               midColor.getDarker(0.15).toString(),
60056                               midColor.toString(),
60057                               midColor.getLighter(0.15).toString(),
60058                               midColor.getLighter(0.3).toString()];
60059
60060             delete config.baseColor;
60061         }
60062         if (config.colors) {
60063             colors = config.colors.slice();
60064             markerThemes = base.markerThemes;
60065             seriesThemes = base.seriesThemes;
60066             l = colors.length;
60067             base.colors = colors;
60068             for (; i < l; i++) {
60069                 color = colors[i];
60070                 markerTheme = markerThemes[i] || {};
60071                 seriesTheme = seriesThemes[i] || {};
60072                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
60073                 markerThemes[i] = markerTheme;
60074                 seriesThemes[i] = seriesTheme;
60075             }
60076             base.markerThemes = markerThemes.slice(0, l);
60077             base.seriesThemes = seriesThemes.slice(0, l);
60078         //the user is configuring something in particular (either markers, series or pie slices)
60079         }
60080         for (key in base) {
60081             if (key in config) {
60082                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
60083                     Ext.apply(base[key], config[key]);
60084                 } else {
60085                     base[key] = config[key];
60086                 }
60087             }
60088         }
60089         if (config.useGradients) {
60090             colors = base.colors || (function () {
60091                 var ans = [];
60092                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
60093                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
60094                 }
60095                 return ans;
60096             })();
60097             for (i = 0, l = colors.length; i < l; i++) {
60098                 midColor = Ext.draw.Color.fromString(colors[i]);
60099                 if (midColor) {
60100                     color = midColor.getDarker(0.1).toString();
60101                     midColor = midColor.toString();
60102                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
60103                     gradients.push({
60104                         id: key,
60105                         angle: 45,
60106                         stops: {
60107                             0: {
60108                                 color: midColor.toString()
60109                             },
60110                             100: {
60111                                 color: color.toString()
60112                             }
60113                         }
60114                     });
60115                     colors[i] = 'url(#' + key + ')'; 
60116                 }
60117             }
60118             base.gradients = gradients;
60119             base.colors = colors;
60120         }
60121         /*
60122         base.axis = Ext.apply(base.axis || {}, config.axis || {});
60123         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
60124         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
60125         */
60126         Ext.apply(this, base);
60127     };
60128 })();
60129 });
60130
60131 /**
60132  * @class Ext.chart.Mask
60133  *
60134  * Defines a mask for a chart's series.
60135  * The 'chart' member must be set prior to rendering.
60136  *
60137  * A Mask can be used to select a certain region in a chart.
60138  * When enabled, the `select` event will be triggered when a
60139  * region is selected by the mask, allowing the user to perform
60140  * other tasks like zooming on that region, etc.
60141  *
60142  * In order to use the mask one has to set the Chart `mask` option to
60143  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
60144  * listener could be:
60145  *
60146         items: {
60147             xtype: 'chart',
60148             animate: true,
60149             store: store1,
60150             mask: 'horizontal',
60151             listeners: {
60152                 select: {
60153                     fn: function(me, selection) {
60154                         me.setZoom(selection);
60155                         me.mask.hide();
60156                     }
60157                 }
60158             },
60159
60160  * In this example we zoom the chart to that particular region. You can also get
60161  * a handle to a mask instance from the chart object. The `chart.mask` element is a
60162  * `Ext.Panel`.
60163  * 
60164  */
60165 Ext.define('Ext.chart.Mask', {
60166     require: ['Ext.chart.MaskLayer'],
60167     /**
60168      * Creates new Mask.
60169      * @param {Object} config (optional) Config object.
60170      */
60171     constructor: function(config) {
60172         var me = this;
60173
60174         me.addEvents('select');
60175
60176         if (config) {
60177             Ext.apply(me, config);
60178         }
60179         if (me.mask) {
60180             me.on('afterrender', function() {
60181                 //create a mask layer component
60182                 var comp = Ext.create('Ext.chart.MaskLayer', {
60183                     renderTo: me.el
60184                 });
60185                 comp.el.on({
60186                     'mousemove': function(e) {
60187                         me.onMouseMove(e);
60188                     },
60189                     'mouseup': function(e) {
60190                         me.resized(e);
60191                     }
60192                 });
60193                 //create a resize handler for the component
60194                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
60195                     el: comp.el,
60196                     handles: 'all',
60197                     pinned: true
60198                 });
60199                 resizeHandler.on({
60200                     'resize': function(e) {
60201                         me.resized(e);    
60202                     }    
60203                 });
60204                 comp.initDraggable();
60205                 me.maskType = me.mask;
60206                 me.mask = comp;
60207                 me.maskSprite = me.surface.add({
60208                     type: 'path',
60209                     path: ['M', 0, 0],
60210                     zIndex: 1001,
60211                     opacity: 0.7,
60212                     hidden: true,
60213                     stroke: '#444'
60214                 });
60215             }, me, { single: true });
60216         }
60217     },
60218     
60219     resized: function(e) {
60220         var me = this,
60221             bbox = me.bbox || me.chartBBox,
60222             x = bbox.x,
60223             y = bbox.y,
60224             width = bbox.width,
60225             height = bbox.height,
60226             box = me.mask.getBox(true),
60227             max = Math.max,
60228             min = Math.min,
60229             staticX = box.x - x,
60230             staticY = box.y - y;
60231         
60232         staticX = max(staticX, x);
60233         staticY = max(staticY, y);
60234         staticX = min(staticX, width);
60235         staticY = min(staticY, height);
60236         box.x = staticX;
60237         box.y = staticY;
60238         me.fireEvent('select', me, box);
60239     },
60240
60241     onMouseUp: function(e) {
60242         var me = this,
60243             bbox = me.bbox || me.chartBBox,
60244             sel = me.maskSelection;
60245         me.maskMouseDown = false;
60246         me.mouseDown = false;
60247         if (me.mouseMoved) {
60248             me.onMouseMove(e);
60249             me.mouseMoved = false;
60250             me.fireEvent('select', me, {
60251                 x: sel.x - bbox.x,
60252                 y: sel.y - bbox.y,
60253                 width: sel.width,
60254                 height: sel.height
60255             });
60256         }
60257     },
60258
60259     onMouseDown: function(e) {
60260         var me = this;
60261         me.mouseDown = true;
60262         me.mouseMoved = false;
60263         me.maskMouseDown = {
60264             x: e.getPageX() - me.el.getX(),
60265             y: e.getPageY() - me.el.getY()
60266         };
60267     },
60268
60269     onMouseMove: function(e) {
60270         var me = this,
60271             mask = me.maskType,
60272             bbox = me.bbox || me.chartBBox,
60273             x = bbox.x,
60274             y = bbox.y,
60275             math = Math,
60276             floor = math.floor,
60277             abs = math.abs,
60278             min = math.min,
60279             max = math.max,
60280             height = floor(y + bbox.height),
60281             width = floor(x + bbox.width),
60282             posX = e.getPageX(),
60283             posY = e.getPageY(),
60284             staticX = posX - me.el.getX(),
60285             staticY = posY - me.el.getY(),
60286             maskMouseDown = me.maskMouseDown,
60287             path;
60288         
60289         me.mouseMoved = me.mouseDown;
60290         staticX = max(staticX, x);
60291         staticY = max(staticY, y);
60292         staticX = min(staticX, width);
60293         staticY = min(staticY, height);
60294         if (maskMouseDown && me.mouseDown) {
60295             if (mask == 'horizontal') {
60296                 staticY = y;
60297                 maskMouseDown.y = height;
60298                 posY = me.el.getY() + bbox.height + me.insetPadding;
60299             }
60300             else if (mask == 'vertical') {
60301                 staticX = x;
60302                 maskMouseDown.x = width;
60303             }
60304             width = maskMouseDown.x - staticX;
60305             height = maskMouseDown.y - staticY;
60306             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
60307             me.maskSelection = {
60308                 x: width > 0 ? staticX : staticX + width,
60309                 y: height > 0 ? staticY : staticY + height,
60310                 width: abs(width),
60311                 height: abs(height)
60312             };
60313             me.mask.updateBox(me.maskSelection);
60314             me.mask.show();
60315             me.maskSprite.setAttributes({
60316                 hidden: true    
60317             }, true);
60318         }
60319         else {
60320             if (mask == 'horizontal') {
60321                 path = ['M', staticX, y, 'L', staticX, height];
60322             }
60323             else if (mask == 'vertical') {
60324                 path = ['M', x, staticY, 'L', width, staticY];
60325             }
60326             else {
60327                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
60328             }
60329             me.maskSprite.setAttributes({
60330                 path: path,
60331                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
60332                 'stroke-width': mask === true ? 1 : 3,
60333                 hidden: false
60334             }, true);
60335         }
60336     },
60337
60338     onMouseLeave: function(e) {
60339         var me = this;
60340         me.mouseMoved = false;
60341         me.mouseDown = false;
60342         me.maskMouseDown = false;
60343         me.mask.hide();
60344         me.maskSprite.hide(true);
60345     }
60346 });
60347     
60348 /**
60349  * @class Ext.chart.Navigation
60350  *
60351  * Handles panning and zooming capabilities.
60352  *
60353  * Used as mixin by Ext.chart.Chart.
60354  */
60355 Ext.define('Ext.chart.Navigation', {
60356
60357     constructor: function() {
60358         this.originalStore = this.store;
60359     },
60360
60361     /**
60362      * Zooms the chart to the specified selection range.
60363      * Can be used with a selection mask. For example:
60364      *
60365      *     items: {
60366      *         xtype: 'chart',
60367      *         animate: true,
60368      *         store: store1,
60369      *         mask: 'horizontal',
60370      *         listeners: {
60371      *             select: {
60372      *                 fn: function(me, selection) {
60373      *                     me.setZoom(selection);
60374      *                     me.mask.hide();
60375      *                 }
60376      *             }
60377      *         }
60378      *     }
60379      */
60380     setZoom: function(zoomConfig) {
60381         var me = this,
60382             axes = me.axes,
60383             bbox = me.chartBBox,
60384             xScale = 1 / bbox.width,
60385             yScale = 1 / bbox.height,
60386             zoomer = {
60387                 x : zoomConfig.x * xScale,
60388                 y : zoomConfig.y * yScale,
60389                 width : zoomConfig.width * xScale,
60390                 height : zoomConfig.height * yScale
60391             };
60392         axes.each(function(axis) {
60393             var ends = axis.calcEnds();
60394             if (axis.position == 'bottom' || axis.position == 'top') {
60395                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
60396                     to = (ends.to - ends.from) * zoomer.width + from;
60397                 axis.minimum = from;
60398                 axis.maximum = to;
60399             } else {
60400                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
60401                     from = to - (ends.to - ends.from) * zoomer.height;
60402                 axis.minimum = from;
60403                 axis.maximum = to;
60404             }
60405         });
60406         me.redraw(false);
60407     },
60408
60409     /**
60410      * Restores the zoom to the original value. This can be used to reset
60411      * the previous zoom state set by `setZoom`. For example:
60412      *
60413      *     myChart.restoreZoom();
60414      */
60415     restoreZoom: function() {
60416         this.store = this.substore = this.originalStore;
60417         this.redraw(true);
60418     }
60419
60420 });
60421
60422 /**
60423  * @class Ext.chart.Shape
60424  * @ignore
60425  */
60426 Ext.define('Ext.chart.Shape', {
60427
60428     /* Begin Definitions */
60429
60430     singleton: true,
60431
60432     /* End Definitions */
60433
60434     circle: function (surface, opts) {
60435         return surface.add(Ext.apply({
60436             type: 'circle',
60437             x: opts.x,
60438             y: opts.y,
60439             stroke: null,
60440             radius: opts.radius
60441         }, opts));
60442     },
60443     line: function (surface, opts) {
60444         return surface.add(Ext.apply({
60445             type: 'rect',
60446             x: opts.x - opts.radius,
60447             y: opts.y - opts.radius,
60448             height: 2 * opts.radius,
60449             width: 2 * opts.radius / 5
60450         }, opts));
60451     },
60452     square: function (surface, opts) {
60453         return surface.add(Ext.applyIf({
60454             type: 'rect',
60455             x: opts.x - opts.radius,
60456             y: opts.y - opts.radius,
60457             height: 2 * opts.radius,
60458             width: 2 * opts.radius,
60459             radius: null
60460         }, opts));
60461     },
60462     triangle: function (surface, opts) {
60463         opts.radius *= 1.75;
60464         return surface.add(Ext.apply({
60465             type: 'path',
60466             stroke: null,
60467             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
60468         }, opts));
60469     },
60470     diamond: function (surface, opts) {
60471         var r = opts.radius;
60472         r *= 1.5;
60473         return surface.add(Ext.apply({
60474             type: 'path',
60475             stroke: null,
60476             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
60477         }, opts));
60478     },
60479     cross: function (surface, opts) {
60480         var r = opts.radius;
60481         r = r / 1.7;
60482         return surface.add(Ext.apply({
60483             type: 'path',
60484             stroke: null,
60485             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"])
60486         }, opts));
60487     },
60488     plus: function (surface, opts) {
60489         var r = opts.radius / 1.3;
60490         return surface.add(Ext.apply({
60491             type: 'path',
60492             stroke: null,
60493             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"])
60494         }, opts));
60495     },
60496     arrow: function (surface, opts) {
60497         var r = opts.radius;
60498         return surface.add(Ext.apply({
60499             type: 'path',
60500             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")
60501         }, opts));
60502     },
60503     drop: function (surface, x, y, text, size, angle) {
60504         size = size || 30;
60505         angle = angle || 0;
60506         surface.add({
60507             type: 'path',
60508             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'],
60509             fill: '#000',
60510             stroke: 'none',
60511             rotate: {
60512                 degrees: 22.5 - angle,
60513                 x: x,
60514                 y: y
60515             }
60516         });
60517         angle = (angle + 90) * Math.PI / 180;
60518         surface.add({
60519             type: 'text',
60520             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
60521             y: y + size * Math.cos(angle) + 5,
60522             text:  text,
60523             'font-size': size * 12 / 40,
60524             stroke: 'none',
60525             fill: '#fff'
60526         });
60527     }
60528 });
60529 /**
60530  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
60531  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
60532  * sprites to the canvas, initialize other graphic components, etc. One of the most used
60533  * methods for this class is the `add` method, to add Sprites to the surface.
60534  *
60535  * Most of the Surface methods are abstract and they have a concrete implementation
60536  * in VML or SVG engines.
60537  *
60538  * A Surface instance can be accessed as a property of a draw component. For example:
60539  *
60540  *     drawComponent.surface.add({
60541  *         type: 'circle',
60542  *         fill: '#ffc',
60543  *         radius: 100,
60544  *         x: 100,
60545  *         y: 100
60546  *     });
60547  *
60548  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
60549  * class documentation.
60550  *
60551  * # Listeners
60552  *
60553  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
60554  *
60555  * - mousedown
60556  * - mouseup
60557  * - mouseover
60558  * - mouseout
60559  * - mousemove
60560  * - mouseenter
60561  * - mouseleave
60562  * - click
60563  *
60564  * For example:
60565  *
60566  *     drawComponent.surface.on({
60567  *        'mousemove': function() {
60568  *             console.log('moving the mouse over the surface');
60569  *         }
60570  *     });
60571  *
60572  * # Example
60573  *
60574  *     var drawComponent = Ext.create('Ext.draw.Component', {
60575  *         width: 800,
60576  *         height: 600,
60577  *         renderTo: document.body
60578  *     }), surface = drawComponent.surface;
60579  *
60580  *     surface.add([{
60581  *         type: 'circle',
60582  *         radius: 10,
60583  *         fill: '#f00',
60584  *         x: 10,
60585  *         y: 10,
60586  *         group: 'circles'
60587  *     }, {
60588  *         type: 'circle',
60589  *         radius: 10,
60590  *         fill: '#0f0',
60591  *         x: 50,
60592  *         y: 50,
60593  *         group: 'circles'
60594  *     }, {
60595  *         type: 'circle',
60596  *         radius: 10,
60597  *         fill: '#00f',
60598  *         x: 100,
60599  *         y: 100,
60600  *         group: 'circles'
60601  *     }, {
60602  *         type: 'rect',
60603  *         width: 20,
60604  *         height: 20,
60605  *         fill: '#f00',
60606  *         x: 10,
60607  *         y: 10,
60608  *         group: 'rectangles'
60609  *     }, {
60610  *         type: 'rect',
60611  *         width: 20,
60612  *         height: 20,
60613  *         fill: '#0f0',
60614  *         x: 50,
60615  *         y: 50,
60616  *         group: 'rectangles'
60617  *     }, {
60618  *         type: 'rect',
60619  *         width: 20,
60620  *         height: 20,
60621  *         fill: '#00f',
60622  *         x: 100,
60623  *         y: 100,
60624  *         group: 'rectangles'
60625  *     }]);
60626  *
60627  *     // Get references to my groups
60628  *     circles = surface.getGroup('circles');
60629  *     rectangles = surface.getGroup('rectangles');
60630  *
60631  *     // Animate the circles down
60632  *     circles.animate({
60633  *         duration: 1000,
60634  *         to: {
60635  *             translate: {
60636  *                 y: 200
60637  *             }
60638  *         }
60639  *     });
60640  *
60641  *     // Animate the rectangles across
60642  *     rectangles.animate({
60643  *         duration: 1000,
60644  *         to: {
60645  *             translate: {
60646  *                 x: 200
60647  *             }
60648  *         }
60649  *     });
60650  */
60651 Ext.define('Ext.draw.Surface', {
60652
60653     /* Begin Definitions */
60654
60655     mixins: {
60656         observable: 'Ext.util.Observable'
60657     },
60658
60659     requires: ['Ext.draw.CompositeSprite'],
60660     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
60661
60662     separatorRe: /[, ]+/,
60663
60664     statics: {
60665         /**
60666          * Creates and returns a new concrete Surface instance appropriate for the current environment.
60667          * @param {Object} config Initial configuration for the Surface instance
60668          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
60669          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
60670          * @return {Object} The created Surface or false.
60671          * @static
60672          */
60673         create: function(config, enginePriority) {
60674             enginePriority = enginePriority || ['Svg', 'Vml'];
60675
60676             var i = 0,
60677                 len = enginePriority.length,
60678                 surfaceClass;
60679
60680             for (; i < len; i++) {
60681                 if (Ext.supports[enginePriority[i]]) {
60682                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
60683                 }
60684             }
60685             return false;
60686         }
60687     },
60688
60689     /* End Definitions */
60690
60691     // @private
60692     availableAttrs: {
60693         blur: 0,
60694         "clip-rect": "0 0 1e9 1e9",
60695         cursor: "default",
60696         cx: 0,
60697         cy: 0,
60698         'dominant-baseline': 'auto',
60699         fill: "none",
60700         "fill-opacity": 1,
60701         font: '10px "Arial"',
60702         "font-family": '"Arial"',
60703         "font-size": "10",
60704         "font-style": "normal",
60705         "font-weight": 400,
60706         gradient: "",
60707         height: 0,
60708         hidden: false,
60709         href: "http://sencha.com/",
60710         opacity: 1,
60711         path: "M0,0",
60712         radius: 0,
60713         rx: 0,
60714         ry: 0,
60715         scale: "1 1",
60716         src: "",
60717         stroke: "#000",
60718         "stroke-dasharray": "",
60719         "stroke-linecap": "butt",
60720         "stroke-linejoin": "butt",
60721         "stroke-miterlimit": 0,
60722         "stroke-opacity": 1,
60723         "stroke-width": 1,
60724         target: "_blank",
60725         text: "",
60726         "text-anchor": "middle",
60727         title: "Ext Draw",
60728         width: 0,
60729         x: 0,
60730         y: 0,
60731         zIndex: 0
60732     },
60733
60734     /**
60735      * @cfg {Number} height
60736      * The height of this component in pixels (defaults to auto).
60737      */
60738     /**
60739      * @cfg {Number} width
60740      * The width of this component in pixels (defaults to auto).
60741      */
60742
60743     container: undefined,
60744     height: 352,
60745     width: 512,
60746     x: 0,
60747     y: 0,
60748
60749     /**
60750      * @private Flag indicating that the surface implementation requires sprites to be maintained
60751      * in order of their zIndex. Impls that don't require this can set it to false.
60752      */
60753     orderSpritesByZIndex: true,
60754
60755
60756     /**
60757      * Creates new Surface.
60758      * @param {Object} config (optional) Config object.
60759      */
60760     constructor: function(config) {
60761         var me = this;
60762         config = config || {};
60763         Ext.apply(me, config);
60764
60765         me.domRef = Ext.getDoc().dom;
60766
60767         me.customAttributes = {};
60768
60769         me.addEvents(
60770             'mousedown',
60771             'mouseup',
60772             'mouseover',
60773             'mouseout',
60774             'mousemove',
60775             'mouseenter',
60776             'mouseleave',
60777             'click'
60778         );
60779
60780         me.mixins.observable.constructor.call(me);
60781
60782         me.getId();
60783         me.initGradients();
60784         me.initItems();
60785         if (me.renderTo) {
60786             me.render(me.renderTo);
60787             delete me.renderTo;
60788         }
60789         me.initBackground(config.background);
60790     },
60791
60792     // @private called to initialize components in the surface
60793     // this is dependent on the underlying implementation.
60794     initSurface: Ext.emptyFn,
60795
60796     // @private called to setup the surface to render an item
60797     //this is dependent on the underlying implementation.
60798     renderItem: Ext.emptyFn,
60799
60800     // @private
60801     renderItems: Ext.emptyFn,
60802
60803     // @private
60804     setViewBox: function (x, y, width, height) {
60805         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
60806             this.viewBox = {x: x, y: y, width: width, height: height};
60807             this.applyViewBox();
60808         }
60809     },
60810
60811     /**
60812      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
60813      *
60814      * For example:
60815      *
60816      *     drawComponent.surface.addCls(sprite, 'x-visible');
60817      *
60818      * @param {Object} sprite The sprite to add the class to.
60819      * @param {String/String[]} className The CSS class to add, or an array of classes
60820      * @method
60821      */
60822     addCls: Ext.emptyFn,
60823
60824     /**
60825      * Removes one or more CSS classes from the element.
60826      *
60827      * For example:
60828      *
60829      *     drawComponent.surface.removeCls(sprite, 'x-visible');
60830      *
60831      * @param {Object} sprite The sprite to remove the class from.
60832      * @param {String/String[]} className The CSS class to remove, or an array of classes
60833      * @method
60834      */
60835     removeCls: Ext.emptyFn,
60836
60837     /**
60838      * Sets CSS style attributes to an element.
60839      *
60840      * For example:
60841      *
60842      *     drawComponent.surface.setStyle(sprite, {
60843      *         'cursor': 'pointer'
60844      *     });
60845      *
60846      * @param {Object} sprite The sprite to add, or an array of classes to
60847      * @param {Object} styles An Object with CSS styles.
60848      * @method
60849      */
60850     setStyle: Ext.emptyFn,
60851
60852     // @private
60853     initGradients: function() {
60854         var gradients = this.gradients;
60855         if (gradients) {
60856             Ext.each(gradients, this.addGradient, this);
60857         }
60858     },
60859
60860     // @private
60861     initItems: function() {
60862         var items = this.items;
60863         this.items = Ext.create('Ext.draw.CompositeSprite');
60864         this.groups = Ext.create('Ext.draw.CompositeSprite');
60865         if (items) {
60866             this.add(items);
60867         }
60868     },
60869
60870     // @private
60871     initBackground: function(config) {
60872         var me = this,
60873             width = me.width,
60874             height = me.height,
60875             gradientId, gradient, backgroundSprite;
60876         if (config) {
60877             if (config.gradient) {
60878                 gradient = config.gradient;
60879                 gradientId = gradient.id;
60880                 me.addGradient(gradient);
60881                 me.background = me.add({
60882                     type: 'rect',
60883                     x: 0,
60884                     y: 0,
60885                     width: width,
60886                     height: height,
60887                     fill: 'url(#' + gradientId + ')'
60888                 });
60889             } else if (config.fill) {
60890                 me.background = me.add({
60891                     type: 'rect',
60892                     x: 0,
60893                     y: 0,
60894                     width: width,
60895                     height: height,
60896                     fill: config.fill
60897                 });
60898             } else if (config.image) {
60899                 me.background = me.add({
60900                     type: 'image',
60901                     x: 0,
60902                     y: 0,
60903                     width: width,
60904                     height: height,
60905                     src: config.image
60906                 });
60907             }
60908         }
60909     },
60910
60911     /**
60912      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
60913      *
60914      * For example:
60915      *
60916      *     drawComponent.surface.setSize(500, 500);
60917      *
60918      * This method is generally called when also setting the size of the draw Component.
60919      *
60920      * @param {Number} w The new width of the canvas.
60921      * @param {Number} h The new height of the canvas.
60922      */
60923     setSize: function(w, h) {
60924         if (this.background) {
60925             this.background.setAttributes({
60926                 width: w,
60927                 height: h,
60928                 hidden: false
60929             }, true);
60930         }
60931         this.applyViewBox();
60932     },
60933
60934     // @private
60935     scrubAttrs: function(sprite) {
60936         var i,
60937             attrs = {},
60938             exclude = {},
60939             sattr = sprite.attr;
60940         for (i in sattr) {
60941             // Narrow down attributes to the main set
60942             if (this.translateAttrs.hasOwnProperty(i)) {
60943                 // Translated attr
60944                 attrs[this.translateAttrs[i]] = sattr[i];
60945                 exclude[this.translateAttrs[i]] = true;
60946             }
60947             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
60948                 // Passtrhough attr
60949                 attrs[i] = sattr[i];
60950             }
60951         }
60952         return attrs;
60953     },
60954
60955     // @private
60956     onClick: function(e) {
60957         this.processEvent('click', e);
60958     },
60959
60960     // @private
60961     onMouseUp: function(e) {
60962         this.processEvent('mouseup', e);
60963     },
60964
60965     // @private
60966     onMouseDown: function(e) {
60967         this.processEvent('mousedown', e);
60968     },
60969
60970     // @private
60971     onMouseOver: function(e) {
60972         this.processEvent('mouseover', e);
60973     },
60974
60975     // @private
60976     onMouseOut: function(e) {
60977         this.processEvent('mouseout', e);
60978     },
60979
60980     // @private
60981     onMouseMove: function(e) {
60982         this.fireEvent('mousemove', e);
60983     },
60984
60985     // @private
60986     onMouseEnter: Ext.emptyFn,
60987
60988     // @private
60989     onMouseLeave: Ext.emptyFn,
60990
60991     /**
60992      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
60993      * a gradient via this method will not take effect if the surface has already been rendered.
60994      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
60995      * than calling this method, especially if the surface is rendered immediately (e.g. due to
60996      * 'renderTo' in its config). For more information on how to create gradients in the Chart
60997      * configuration object please refer to {@link Ext.chart.Chart}.
60998      *
60999      * The gradient object to be passed into this method is composed by:
61000      *
61001      * - **id** - string - The unique name of the gradient.
61002      * - **angle** - number, optional - The angle of the gradient in degrees.
61003      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
61004      *
61005      * For example:
61006      *
61007      *    drawComponent.surface.addGradient({
61008      *        id: 'gradientId',
61009      *        angle: 45,
61010      *        stops: {
61011      *            0: {
61012      *                color: '#555'
61013      *            },
61014      *            100: {
61015      *                color: '#ddd'
61016      *            }
61017      *        }
61018      *    });
61019      *
61020      * @method
61021      */
61022     addGradient: Ext.emptyFn,
61023
61024     /**
61025      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
61026      * passed into this method.
61027      *
61028      * For example:
61029      *
61030      *     drawComponent.surface.add({
61031      *         type: 'circle',
61032      *         fill: '#ffc',
61033      *         radius: 100,
61034      *         x: 100,
61035      *         y: 100
61036      *     });
61037      *
61038      */
61039     add: function() {
61040         var args = Array.prototype.slice.call(arguments),
61041             sprite,
61042             index;
61043
61044         var hasMultipleArgs = args.length > 1;
61045         if (hasMultipleArgs || Ext.isArray(args[0])) {
61046             var items = hasMultipleArgs ? args : args[0],
61047                 results = [],
61048                 i, ln, item;
61049
61050             for (i = 0, ln = items.length; i < ln; i++) {
61051                 item = items[i];
61052                 item = this.add(item);
61053                 results.push(item);
61054             }
61055
61056             return results;
61057         }
61058         sprite = this.prepareItems(args[0], true)[0];
61059         this.insertByZIndex(sprite);
61060         this.onAdd(sprite);
61061         return sprite;
61062     },
61063
61064     /**
61065      * @private
61066      * Inserts a given sprite into the correct position in the items collection, according to
61067      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
61068      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
61069      * the sprites in the correct order for proper z-index stacking.
61070      * @param {Ext.draw.Sprite} sprite
61071      * @return {Number} the sprite's new index in the list
61072      */
61073     insertByZIndex: function(sprite) {
61074         var me = this,
61075             sprites = me.items.items,
61076             len = sprites.length,
61077             ceil = Math.ceil,
61078             zIndex = sprite.attr.zIndex,
61079             idx = len,
61080             high = idx - 1,
61081             low = 0,
61082             otherZIndex;
61083
61084         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
61085             // Find the target index via a binary search for speed
61086             while (low <= high) {
61087                 idx = ceil((low + high) / 2);
61088                 otherZIndex = sprites[idx].attr.zIndex;
61089                 if (otherZIndex > zIndex) {
61090                     high = idx - 1;
61091                 }
61092                 else if (otherZIndex < zIndex) {
61093                     low = idx + 1;
61094                 }
61095                 else {
61096                     break;
61097                 }
61098             }
61099             // Step forward to the end of a sequence of the same or lower z-index
61100             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
61101                 idx++;
61102             }
61103         }
61104
61105         me.items.insert(idx, sprite);
61106         return idx;
61107     },
61108
61109     onAdd: function(sprite) {
61110         var group = sprite.group,
61111             draggable = sprite.draggable,
61112             groups, ln, i;
61113         if (group) {
61114             groups = [].concat(group);
61115             ln = groups.length;
61116             for (i = 0; i < ln; i++) {
61117                 group = groups[i];
61118                 this.getGroup(group).add(sprite);
61119             }
61120             delete sprite.group;
61121         }
61122         if (draggable) {
61123             sprite.initDraggable();
61124         }
61125     },
61126
61127     /**
61128      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
61129      * You can also call the sprite own `remove` method.
61130      *
61131      * For example:
61132      *
61133      *     drawComponent.surface.remove(sprite);
61134      *     //or...
61135      *     sprite.remove();
61136      *
61137      * @param {Ext.draw.Sprite} sprite
61138      * @param {Boolean} destroySprite
61139      * @return {Number} the sprite's new index in the list
61140      */
61141     remove: function(sprite, destroySprite) {
61142         if (sprite) {
61143             this.items.remove(sprite);
61144             this.groups.each(function(item) {
61145                 item.remove(sprite);
61146             });
61147             sprite.onRemove();
61148             if (destroySprite === true) {
61149                 sprite.destroy();
61150             }
61151         }
61152     },
61153
61154     /**
61155      * Removes all sprites from the surface, optionally destroying the sprites in the process.
61156      *
61157      * For example:
61158      *
61159      *     drawComponent.surface.removeAll();
61160      *
61161      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
61162      * @return {Number} The sprite's new index in the list.
61163      */
61164     removeAll: function(destroySprites) {
61165         var items = this.items.items,
61166             ln = items.length,
61167             i;
61168         for (i = ln - 1; i > -1; i--) {
61169             this.remove(items[i], destroySprites);
61170         }
61171     },
61172
61173     onRemove: Ext.emptyFn,
61174
61175     onDestroy: Ext.emptyFn,
61176
61177     /**
61178      * @private Using the current viewBox property and the surface's width and height, calculate the
61179      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
61180      */
61181     applyViewBox: function() {
61182         var me = this,
61183             viewBox = me.viewBox,
61184             width = me.width,
61185             height = me.height,
61186             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
61187             relativeHeight, relativeWidth, size;
61188
61189         if (viewBox && (width || height)) {
61190             viewBoxX = viewBox.x;
61191             viewBoxY = viewBox.y;
61192             viewBoxWidth = viewBox.width;
61193             viewBoxHeight = viewBox.height;
61194             relativeHeight = height / viewBoxHeight;
61195             relativeWidth = width / viewBoxWidth;
61196
61197             if (viewBoxWidth * relativeHeight < width) {
61198                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
61199             }
61200             if (viewBoxHeight * relativeWidth < height) {
61201                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
61202             }
61203
61204             size = 1 / Math.min(viewBoxWidth, relativeHeight);
61205
61206             me.viewBoxShift = {
61207                 dx: -viewBoxX,
61208                 dy: -viewBoxY,
61209                 scale: size
61210             };
61211         }
61212     },
61213
61214     transformToViewBox: function (x, y) {
61215         if (this.viewBoxShift) {
61216             var me = this, shift = me.viewBoxShift;
61217             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
61218         } else {
61219             return [x, y];
61220         }
61221     },
61222
61223     // @private
61224     applyTransformations: function(sprite) {
61225             sprite.bbox.transform = 0;
61226             this.transform(sprite);
61227
61228         var me = this,
61229             dirty = false,
61230             attr = sprite.attr;
61231
61232         if (attr.translation.x != null || attr.translation.y != null) {
61233             me.translate(sprite);
61234             dirty = true;
61235         }
61236         if (attr.scaling.x != null || attr.scaling.y != null) {
61237             me.scale(sprite);
61238             dirty = true;
61239         }
61240         if (attr.rotation.degrees != null) {
61241             me.rotate(sprite);
61242             dirty = true;
61243         }
61244         if (dirty) {
61245             sprite.bbox.transform = 0;
61246             this.transform(sprite);
61247             sprite.transformations = [];
61248         }
61249     },
61250
61251     // @private
61252     rotate: function (sprite) {
61253         var bbox,
61254             deg = sprite.attr.rotation.degrees,
61255             centerX = sprite.attr.rotation.x,
61256             centerY = sprite.attr.rotation.y;
61257         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61258             bbox = this.getBBox(sprite);
61259             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61260             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61261         }
61262         sprite.transformations.push({
61263             type: "rotate",
61264             degrees: deg,
61265             x: centerX,
61266             y: centerY
61267         });
61268     },
61269
61270     // @private
61271     translate: function(sprite) {
61272         var x = sprite.attr.translation.x || 0,
61273             y = sprite.attr.translation.y || 0;
61274         sprite.transformations.push({
61275             type: "translate",
61276             x: x,
61277             y: y
61278         });
61279     },
61280
61281     // @private
61282     scale: function(sprite) {
61283         var bbox,
61284             x = sprite.attr.scaling.x || 1,
61285             y = sprite.attr.scaling.y || 1,
61286             centerX = sprite.attr.scaling.centerX,
61287             centerY = sprite.attr.scaling.centerY;
61288
61289         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61290             bbox = this.getBBox(sprite);
61291             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61292             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61293         }
61294         sprite.transformations.push({
61295             type: "scale",
61296             x: x,
61297             y: y,
61298             centerX: centerX,
61299             centerY: centerY
61300         });
61301     },
61302
61303     // @private
61304     rectPath: function (x, y, w, h, r) {
61305         if (r) {
61306             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"]];
61307         }
61308         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
61309     },
61310
61311     // @private
61312     ellipsePath: function (x, y, rx, ry) {
61313         if (ry == null) {
61314             ry = rx;
61315         }
61316         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"]];
61317     },
61318
61319     // @private
61320     getPathpath: function (el) {
61321         return el.attr.path;
61322     },
61323
61324     // @private
61325     getPathcircle: function (el) {
61326         var a = el.attr;
61327         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
61328     },
61329
61330     // @private
61331     getPathellipse: function (el) {
61332         var a = el.attr;
61333         return this.ellipsePath(a.x, a.y,
61334                                 a.radiusX || (a.width / 2) || 0,
61335                                 a.radiusY || (a.height / 2) || 0);
61336     },
61337
61338     // @private
61339     getPathrect: function (el) {
61340         var a = el.attr;
61341         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
61342     },
61343
61344     // @private
61345     getPathimage: function (el) {
61346         var a = el.attr;
61347         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
61348     },
61349
61350     // @private
61351     getPathtext: function (el) {
61352         var bbox = this.getBBoxText(el);
61353         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
61354     },
61355
61356     createGroup: function(id) {
61357         var group = this.groups.get(id);
61358         if (!group) {
61359             group = Ext.create('Ext.draw.CompositeSprite', {
61360                 surface: this
61361             });
61362             group.id = id || Ext.id(null, 'ext-surface-group-');
61363             this.groups.add(group);
61364         }
61365         return group;
61366     },
61367
61368     /**
61369      * Returns a new group or an existent group associated with the current surface.
61370      * The group returned is a {@link Ext.draw.CompositeSprite} group.
61371      *
61372      * For example:
61373      *
61374      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
61375      *
61376      * @param {String} id The unique identifier of the group.
61377      * @return {Object} The {@link Ext.draw.CompositeSprite}.
61378      */
61379     getGroup: function(id) {
61380         if (typeof id == "string") {
61381             var group = this.groups.get(id);
61382             if (!group) {
61383                 group = this.createGroup(id);
61384             }
61385         } else {
61386             group = id;
61387         }
61388         return group;
61389     },
61390
61391     // @private
61392     prepareItems: function(items, applyDefaults) {
61393         items = [].concat(items);
61394         // Make sure defaults are applied and item is initialized
61395         var item, i, ln;
61396         for (i = 0, ln = items.length; i < ln; i++) {
61397             item = items[i];
61398             if (!(item instanceof Ext.draw.Sprite)) {
61399                 // Temporary, just take in configs...
61400                 item.surface = this;
61401                 items[i] = this.createItem(item);
61402             } else {
61403                 item.surface = this;
61404             }
61405         }
61406         return items;
61407     },
61408
61409     /**
61410      * Changes the text in the sprite element. The sprite must be a `text` sprite.
61411      * This method can also be called from {@link Ext.draw.Sprite}.
61412      *
61413      * For example:
61414      *
61415      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
61416      *
61417      * @param {Object} sprite The Sprite to change the text.
61418      * @param {String} text The new text to be set.
61419      * @method
61420      */
61421     setText: Ext.emptyFn,
61422
61423     //@private Creates an item and appends it to the surface. Called
61424     //as an internal method when calling `add`.
61425     createItem: Ext.emptyFn,
61426
61427     /**
61428      * Retrieves the id of this component.
61429      * Will autogenerate an id if one has not already been set.
61430      */
61431     getId: function() {
61432         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
61433     },
61434
61435     /**
61436      * Destroys the surface. This is done by removing all components from it and
61437      * also removing its reference to a DOM element.
61438      *
61439      * For example:
61440      *
61441      *      drawComponent.surface.destroy();
61442      */
61443     destroy: function() {
61444         delete this.domRef;
61445         this.removeAll();
61446     }
61447 });
61448 /**
61449  * @class Ext.layout.component.Draw
61450  * @extends Ext.layout.component.Component
61451  * @private
61452  *
61453  */
61454
61455 Ext.define('Ext.layout.component.Draw', {
61456
61457     /* Begin Definitions */
61458
61459     alias: 'layout.draw',
61460
61461     extend: 'Ext.layout.component.Auto',
61462
61463     /* End Definitions */
61464
61465     type: 'draw',
61466
61467     onLayout : function(width, height) {
61468         this.owner.surface.setSize(width, height);
61469         this.callParent(arguments);
61470     }
61471 });
61472 /**
61473  * @class Ext.draw.Component
61474  * @extends Ext.Component
61475  *
61476  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
61477  * manages and holds a `Surface` instance: an interface that has
61478  * an SVG or VML implementation depending on the browser capabilities and where
61479  * Sprites can be appended.
61480  *
61481  * One way to create a draw component is:
61482  *
61483  *     @example
61484  *     var drawComponent = Ext.create('Ext.draw.Component', {
61485  *         viewBox: false,
61486  *         items: [{
61487  *             type: 'circle',
61488  *             fill: '#79BB3F',
61489  *             radius: 100,
61490  *             x: 100,
61491  *             y: 100
61492  *         }]
61493  *     });
61494  *
61495  *     Ext.create('Ext.Window', {
61496  *         width: 215,
61497  *         height: 235,
61498  *         layout: 'fit',
61499  *         items: [drawComponent]
61500  *     }).show();
61501  *
61502  * In this case we created a draw component and added a sprite to it.
61503  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
61504  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
61505  * dimensions accordingly.
61506  *
61507  * You can also add sprites by using the surface's add method:
61508  *
61509  *     drawComponent.surface.add({
61510  *         type: 'circle',
61511  *         fill: '#79BB3F',
61512  *         radius: 100,
61513  *         x: 100,
61514  *         y: 100
61515  *     });
61516  *
61517  * For more information on Sprites, the core elements added to a draw component's surface,
61518  * refer to the Ext.draw.Sprite documentation.
61519  */
61520 Ext.define('Ext.draw.Component', {
61521
61522     /* Begin Definitions */
61523
61524     alias: 'widget.draw',
61525
61526     extend: 'Ext.Component',
61527
61528     requires: [
61529         'Ext.draw.Surface',
61530         'Ext.layout.component.Draw'
61531     ],
61532
61533     /* End Definitions */
61534
61535     /**
61536      * @cfg {String[]} enginePriority
61537      * Defines the priority order for which Surface implementation to use. The first
61538      * one supported by the current environment will be used.
61539      */
61540     enginePriority: ['Svg', 'Vml'],
61541
61542     baseCls: Ext.baseCSSPrefix + 'surface',
61543
61544     componentLayout: 'draw',
61545
61546     /**
61547      * @cfg {Boolean} viewBox
61548      * Turn on view box support which will scale and position items in the draw component to fit to the component while
61549      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
61550      */
61551     viewBox: true,
61552
61553     /**
61554      * @cfg {Boolean} autoSize
61555      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
61556      */
61557     autoSize: false,
61558
61559     /**
61560      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
61561      * The gradients array is an array of objects with the following properties:
61562      *
61563      *  - `id` - string - The unique name of the gradient.
61564      *  - `angle` - number, optional - The angle of the gradient in degrees.
61565      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
61566      *
61567      * ## Example
61568      *
61569      *     gradients: [{
61570      *         id: 'gradientId',
61571      *         angle: 45,
61572      *         stops: {
61573      *             0: {
61574      *                 color: '#555'
61575      *             },
61576      *             100: {
61577      *                 color: '#ddd'
61578      *             }
61579      *         }
61580      *     }, {
61581      *         id: 'gradientId2',
61582      *         angle: 0,
61583      *         stops: {
61584      *             0: {
61585      *                 color: '#590'
61586      *             },
61587      *             20: {
61588      *                 color: '#599'
61589      *             },
61590      *             100: {
61591      *                 color: '#ddd'
61592      *             }
61593      *         }
61594      *     }]
61595      *
61596      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
61597      *
61598      *     sprite.setAttributes({
61599      *         fill: 'url(#gradientId)'
61600      *     }, true);
61601      */
61602     initComponent: function() {
61603         this.callParent(arguments);
61604
61605         this.addEvents(
61606             'mousedown',
61607             'mouseup',
61608             'mousemove',
61609             'mouseenter',
61610             'mouseleave',
61611             'click'
61612         );
61613     },
61614
61615     /**
61616      * @private
61617      *
61618      * Create the Surface on initial render
61619      */
61620     onRender: function() {
61621         var me = this,
61622             viewBox = me.viewBox,
61623             autoSize = me.autoSize,
61624             bbox, items, width, height, x, y;
61625         me.callParent(arguments);
61626
61627         if (me.createSurface() !== false) {
61628             items = me.surface.items;
61629
61630             if (viewBox || autoSize) {
61631                 bbox = items.getBBox();
61632                 width = bbox.width;
61633                 height = bbox.height;
61634                 x = bbox.x;
61635                 y = bbox.y;
61636                 if (me.viewBox) {
61637                     me.surface.setViewBox(x, y, width, height);
61638                 }
61639                 else {
61640                     // AutoSized
61641                     me.autoSizeSurface();
61642                 }
61643             }
61644         }
61645     },
61646
61647     //@private
61648     autoSizeSurface: function() {
61649         var me = this,
61650             items = me.surface.items,
61651             bbox = items.getBBox(),
61652             width = bbox.width,
61653             height = bbox.height;
61654         items.setAttributes({
61655             translate: {
61656                 x: -bbox.x,
61657                 //Opera has a slight offset in the y axis.
61658                 y: -bbox.y + (+Ext.isOpera)
61659             }
61660         }, true);
61661         if (me.rendered) {
61662             me.setSize(width, height);
61663             me.surface.setSize(width, height);
61664         }
61665         else {
61666             me.surface.setSize(width, height);
61667         }
61668         me.el.setSize(width, height);
61669     },
61670
61671     /**
61672      * Create the Surface instance. Resolves the correct Surface implementation to
61673      * instantiate based on the 'enginePriority' config. Once the Surface instance is
61674      * created you can use the handle to that instance to add sprites. For example:
61675      *
61676      *     drawComponent.surface.add(sprite);
61677      */
61678     createSurface: function() {
61679         var surface = Ext.draw.Surface.create(Ext.apply({}, {
61680                 width: this.width,
61681                 height: this.height,
61682                 renderTo: this.el
61683             }, this.initialConfig));
61684         if (!surface) {
61685             // In case we cannot create a surface, return false so we can stop
61686             return false;
61687         }
61688         this.surface = surface;
61689
61690
61691         function refire(eventName) {
61692             return function(e) {
61693                 this.fireEvent(eventName, e);
61694             };
61695         }
61696
61697         surface.on({
61698             scope: this,
61699             mouseup: refire('mouseup'),
61700             mousedown: refire('mousedown'),
61701             mousemove: refire('mousemove'),
61702             mouseenter: refire('mouseenter'),
61703             mouseleave: refire('mouseleave'),
61704             click: refire('click')
61705         });
61706     },
61707
61708
61709     /**
61710      * @private
61711      *
61712      * Clean up the Surface instance on component destruction
61713      */
61714     onDestroy: function() {
61715         var surface = this.surface;
61716         if (surface) {
61717             surface.destroy();
61718         }
61719         this.callParent(arguments);
61720     }
61721
61722 });
61723
61724 /**
61725  * @class Ext.chart.LegendItem
61726  * @extends Ext.draw.CompositeSprite
61727  * A single item of a legend (marker plus label)
61728  */
61729 Ext.define('Ext.chart.LegendItem', {
61730
61731     /* Begin Definitions */
61732
61733     extend: 'Ext.draw.CompositeSprite',
61734
61735     requires: ['Ext.chart.Shape'],
61736
61737     /* End Definitions */
61738
61739     // Position of the item, relative to the upper-left corner of the legend box
61740     x: 0,
61741     y: 0,
61742     zIndex: 500,
61743
61744     constructor: function(config) {
61745         this.callParent(arguments);
61746         this.createLegend(config);
61747     },
61748
61749     /**
61750      * Creates all the individual sprites for this legend item
61751      */
61752     createLegend: function(config) {
61753         var me = this,
61754             index = config.yFieldIndex,
61755             series = me.series,
61756             seriesType = series.type,
61757             idx = me.yFieldIndex,
61758             legend = me.legend,
61759             surface = me.surface,
61760             refX = legend.x + me.x,
61761             refY = legend.y + me.y,
61762             bbox, z = me.zIndex,
61763             markerConfig, label, mask,
61764             radius, toggle = false,
61765             seriesStyle = Ext.apply(series.seriesStyle, series.style);
61766
61767         function getSeriesProp(name) {
61768             var val = series[name];
61769             return (Ext.isArray(val) ? val[idx] : val);
61770         }
61771         
61772         label = me.add('label', surface.add({
61773             type: 'text',
61774             x: 20,
61775             y: 0,
61776             zIndex: z || 0,
61777             font: legend.labelFont,
61778             text: getSeriesProp('title') || getSeriesProp('yField')
61779         }));
61780
61781         // Line series - display as short line with optional marker in the middle
61782         if (seriesType === 'line' || seriesType === 'scatter') {
61783             if(seriesType === 'line') {
61784                 me.add('line', surface.add({
61785                     type: 'path',
61786                     path: 'M0.5,0.5L16.5,0.5',
61787                     zIndex: z,
61788                     "stroke-width": series.lineWidth,
61789                     "stroke-linejoin": "round",
61790                     "stroke-dasharray": series.dash,
61791                     stroke: seriesStyle.stroke || '#000',
61792                     style: {
61793                         cursor: 'pointer'
61794                     }
61795                 }));
61796             }
61797             if (series.showMarkers || seriesType === 'scatter') {
61798                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
61799                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
61800                     fill: markerConfig.fill,
61801                     x: 8.5,
61802                     y: 0.5,
61803                     zIndex: z,
61804                     radius: markerConfig.radius || markerConfig.size,
61805                     style: {
61806                         cursor: 'pointer'
61807                     }
61808                 }));
61809             }
61810         }
61811         // All other series types - display as filled box
61812         else {
61813             me.add('box', surface.add({
61814                 type: 'rect',
61815                 zIndex: z,
61816                 x: 0,
61817                 y: 0,
61818                 width: 12,
61819                 height: 12,
61820                 fill: series.getLegendColor(index),
61821                 style: {
61822                     cursor: 'pointer'
61823                 }
61824             }));
61825         }
61826         
61827         me.setAttributes({
61828             hidden: false
61829         }, true);
61830         
61831         bbox = me.getBBox();
61832         
61833         mask = me.add('mask', surface.add({
61834             type: 'rect',
61835             x: bbox.x,
61836             y: bbox.y,
61837             width: bbox.width || 20,
61838             height: bbox.height || 20,
61839             zIndex: (z || 0) + 1000,
61840             fill: '#f00',
61841             opacity: 0,
61842             style: {
61843                 'cursor': 'pointer'
61844             }
61845         }));
61846
61847         //add toggle listener
61848         me.on('mouseover', function() {
61849             label.setStyle({
61850                 'font-weight': 'bold'
61851             });
61852             mask.setStyle({
61853                 'cursor': 'pointer'
61854             });
61855             series._index = index;
61856             series.highlightItem();
61857         }, me);
61858
61859         me.on('mouseout', function() {
61860             label.setStyle({
61861                 'font-weight': 'normal'
61862             });
61863             series._index = index;
61864             series.unHighlightItem();
61865         }, me);
61866         
61867         if (!series.visibleInLegend(index)) {
61868             toggle = true;
61869             label.setAttributes({
61870                opacity: 0.5
61871             }, true);
61872         }
61873
61874         me.on('mousedown', function() {
61875             if (!toggle) {
61876                 series.hideAll();
61877                 label.setAttributes({
61878                     opacity: 0.5
61879                 }, true);
61880             } else {
61881                 series.showAll();
61882                 label.setAttributes({
61883                     opacity: 1
61884                 }, true);
61885             }
61886             toggle = !toggle;
61887         }, me);
61888         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
61889     },
61890
61891     /**
61892      * Update the positions of all this item's sprites to match the root position
61893      * of the legend box.
61894      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
61895      *                 as the reference point for the relative positioning. Defaults to the Legend.
61896      */
61897     updatePosition: function(relativeTo) {
61898         var me = this,
61899             items = me.items,
61900             ln = items.length,
61901             i = 0,
61902             item;
61903         if (!relativeTo) {
61904             relativeTo = me.legend;
61905         }
61906         for (; i < ln; i++) {
61907             item = items[i];
61908             switch (item.type) {
61909                 case 'text':
61910                     item.setAttributes({
61911                         x: 20 + relativeTo.x + me.x,
61912                         y: relativeTo.y + me.y
61913                     }, true);
61914                     break;
61915                 case 'rect':
61916                     item.setAttributes({
61917                         translate: {
61918                             x: relativeTo.x + me.x,
61919                             y: relativeTo.y + me.y - 6
61920                         }
61921                     }, true);
61922                     break;
61923                 default:
61924                     item.setAttributes({
61925                         translate: {
61926                             x: relativeTo.x + me.x,
61927                             y: relativeTo.y + me.y
61928                         }
61929                     }, true);
61930             }
61931         }
61932     }
61933 });
61934
61935 /**
61936  * @class Ext.chart.Legend
61937  *
61938  * Defines a legend for a chart's series.
61939  * The 'chart' member must be set prior to rendering.
61940  * The legend class displays a list of legend items each of them related with a
61941  * series being rendered. In order to render the legend item of the proper series
61942  * the series configuration object must have `showInSeries` set to true.
61943  *
61944  * The legend configuration object accepts a `position` as parameter.
61945  * The `position` parameter can be `left`, `right`
61946  * `top` or `bottom`. For example:
61947  *
61948  *     legend: {
61949  *         position: 'right'
61950  *     },
61951  *
61952  * ## Example
61953  *
61954  *     @example
61955  *     var store = Ext.create('Ext.data.JsonStore', {
61956  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61957  *         data: [
61958  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
61959  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
61960  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
61961  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
61962  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
61963  *         ]
61964  *     });
61965  *
61966  *     Ext.create('Ext.chart.Chart', {
61967  *         renderTo: Ext.getBody(),
61968  *         width: 500,
61969  *         height: 300,
61970  *         animate: true,
61971  *         store: store,
61972  *         shadow: true,
61973  *         theme: 'Category1',
61974  *         legend: {
61975  *             position: 'top'
61976  *         },
61977  *         axes: [
61978  *             {
61979  *                 type: 'Numeric',
61980  *                 grid: true,
61981  *                 position: 'left',
61982  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61983  *                 title: 'Sample Values',
61984  *                 grid: {
61985  *                     odd: {
61986  *                         opacity: 1,
61987  *                         fill: '#ddd',
61988  *                         stroke: '#bbb',
61989  *                         'stroke-width': 1
61990  *                     }
61991  *                 },
61992  *                 minimum: 0,
61993  *                 adjustMinimumByMajorUnit: 0
61994  *             },
61995  *             {
61996  *                 type: 'Category',
61997  *                 position: 'bottom',
61998  *                 fields: ['name'],
61999  *                 title: 'Sample Metrics',
62000  *                 grid: true,
62001  *                 label: {
62002  *                     rotate: {
62003  *                         degrees: 315
62004  *                     }
62005  *                 }
62006  *             }
62007  *         ],
62008  *         series: [{
62009  *             type: 'area',
62010  *             highlight: false,
62011  *             axis: 'left',
62012  *             xField: 'name',
62013  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
62014  *             style: {
62015  *                 opacity: 0.93
62016  *             }
62017  *         }]
62018  *     });
62019  */
62020 Ext.define('Ext.chart.Legend', {
62021
62022     /* Begin Definitions */
62023
62024     requires: ['Ext.chart.LegendItem'],
62025
62026     /* End Definitions */
62027
62028     /**
62029      * @cfg {Boolean} visible
62030      * Whether or not the legend should be displayed.
62031      */
62032     visible: true,
62033
62034     /**
62035      * @cfg {String} position
62036      * The position of the legend in relation to the chart. One of: "top",
62037      * "bottom", "left", "right", or "float". If set to "float", then the legend
62038      * box will be positioned at the point denoted by the x and y parameters.
62039      */
62040     position: 'bottom',
62041
62042     /**
62043      * @cfg {Number} x
62044      * X-position of the legend box. Used directly if position is set to "float", otherwise
62045      * it will be calculated dynamically.
62046      */
62047     x: 0,
62048
62049     /**
62050      * @cfg {Number} y
62051      * Y-position of the legend box. Used directly if position is set to "float", otherwise
62052      * it will be calculated dynamically.
62053      */
62054     y: 0,
62055
62056     /**
62057      * @cfg {String} labelFont
62058      * Font to be used for the legend labels, eg '12px Helvetica'
62059      */
62060     labelFont: '12px Helvetica, sans-serif',
62061
62062     /**
62063      * @cfg {String} boxStroke
62064      * Style of the stroke for the legend box
62065      */
62066     boxStroke: '#000',
62067
62068     /**
62069      * @cfg {String} boxStrokeWidth
62070      * Width of the stroke for the legend box
62071      */
62072     boxStrokeWidth: 1,
62073
62074     /**
62075      * @cfg {String} boxFill
62076      * Fill style for the legend box
62077      */
62078     boxFill: '#FFF',
62079
62080     /**
62081      * @cfg {Number} itemSpacing
62082      * Amount of space between legend items
62083      */
62084     itemSpacing: 10,
62085
62086     /**
62087      * @cfg {Number} padding
62088      * Amount of padding between the legend box's border and its items
62089      */
62090     padding: 5,
62091
62092     // @private
62093     width: 0,
62094     // @private
62095     height: 0,
62096
62097     /**
62098      * @cfg {Number} boxZIndex
62099      * Sets the z-index for the legend. Defaults to 100.
62100      */
62101     boxZIndex: 100,
62102
62103     /**
62104      * Creates new Legend.
62105      * @param {Object} config  (optional) Config object.
62106      */
62107     constructor: function(config) {
62108         var me = this;
62109         if (config) {
62110             Ext.apply(me, config);
62111         }
62112         me.items = [];
62113         /**
62114          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
62115          * @type {Boolean}
62116          */
62117         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
62118
62119         // cache these here since they may get modified later on
62120         me.origX = me.x;
62121         me.origY = me.y;
62122     },
62123
62124     /**
62125      * @private Create all the sprites for the legend
62126      */
62127     create: function() {
62128         var me = this;
62129         me.createBox();
62130         me.createItems();
62131         if (!me.created && me.isDisplayed()) {
62132             me.created = true;
62133
62134             // Listen for changes to series titles to trigger regeneration of the legend
62135             me.chart.series.each(function(series) {
62136                 series.on('titlechange', function() {
62137                     me.create();
62138                     me.updatePosition();
62139                 });
62140             });
62141         }
62142     },
62143
62144     /**
62145      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
62146      * and also the 'showInLegend' config for each of the series.
62147      */
62148     isDisplayed: function() {
62149         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
62150     },
62151
62152     /**
62153      * @private Create the series markers and labels
62154      */
62155     createItems: function() {
62156         var me = this,
62157             chart = me.chart,
62158             surface = chart.surface,
62159             items = me.items,
62160             padding = me.padding,
62161             itemSpacing = me.itemSpacing,
62162             spacingOffset = 2,
62163             maxWidth = 0,
62164             maxHeight = 0,
62165             totalWidth = 0,
62166             totalHeight = 0,
62167             vertical = me.isVertical,
62168             math = Math,
62169             mfloor = math.floor,
62170             mmax = math.max,
62171             index = 0,
62172             i = 0,
62173             len = items ? items.length : 0,
62174             x, y, spacing, item, bbox, height, width;
62175
62176         //remove all legend items
62177         if (len) {
62178             for (; i < len; i++) {
62179                 items[i].destroy();
62180             }
62181         }
62182         //empty array
62183         items.length = [];
62184         // Create all the item labels, collecting their dimensions and positioning each one
62185         // properly in relation to the previous item
62186         chart.series.each(function(series, i) {
62187             if (series.showInLegend) {
62188                 Ext.each([].concat(series.yField), function(field, j) {
62189                     item = Ext.create('Ext.chart.LegendItem', {
62190                         legend: this,
62191                         series: series,
62192                         surface: chart.surface,
62193                         yFieldIndex: j
62194                     });
62195                     bbox = item.getBBox();
62196
62197                     //always measure from x=0, since not all markers go all the way to the left
62198                     width = bbox.width;
62199                     height = bbox.height;
62200
62201                     if (i + j === 0) {
62202                         spacing = vertical ? padding + height / 2 : padding;
62203                     }
62204                     else {
62205                         spacing = itemSpacing / (vertical ? 2 : 1);
62206                     }
62207                     // Set the item's position relative to the legend box
62208                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
62209                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
62210
62211                     // Collect cumulative dimensions
62212                     totalWidth += width + spacing;
62213                     totalHeight += height + spacing;
62214                     maxWidth = mmax(maxWidth, width);
62215                     maxHeight = mmax(maxHeight, height);
62216
62217                     items.push(item);
62218                 }, this);
62219             }
62220         }, me);
62221
62222         // Store the collected dimensions for later
62223         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
62224         if (vertical && items.length === 1) {
62225             spacingOffset = 1;
62226         }
62227         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
62228         me.itemHeight = maxHeight;
62229     },
62230
62231     /**
62232      * @private Get the bounds for the legend's outer box
62233      */
62234     getBBox: function() {
62235         var me = this;
62236         return {
62237             x: Math.round(me.x) - me.boxStrokeWidth / 2,
62238             y: Math.round(me.y) - me.boxStrokeWidth / 2,
62239             width: me.width,
62240             height: me.height
62241         };
62242     },
62243
62244     /**
62245      * @private Create the box around the legend items
62246      */
62247     createBox: function() {
62248         var me = this,
62249             box;
62250
62251         if (me.boxSprite) {
62252             me.boxSprite.destroy();
62253         }
62254         
62255         box = me.boxSprite = me.chart.surface.add(Ext.apply({
62256             type: 'rect',
62257             stroke: me.boxStroke,
62258             "stroke-width": me.boxStrokeWidth,
62259             fill: me.boxFill,
62260             zIndex: me.boxZIndex
62261         }, me.getBBox()));
62262
62263         box.redraw();
62264     },
62265
62266     /**
62267      * @private Update the position of all the legend's sprites to match its current x/y values
62268      */
62269     updatePosition: function() {
62270         var me = this,
62271             x, y,
62272             legendWidth = me.width,
62273             legendHeight = me.height,
62274             padding = me.padding,
62275             chart = me.chart,
62276             chartBBox = chart.chartBBox,
62277             insets = chart.insetPadding,
62278             chartWidth = chartBBox.width - (insets * 2),
62279             chartHeight = chartBBox.height - (insets * 2),
62280             chartX = chartBBox.x + insets,
62281             chartY = chartBBox.y + insets,
62282             surface = chart.surface,
62283             mfloor = Math.floor;
62284
62285         if (me.isDisplayed()) {
62286             // Find the position based on the dimensions
62287             switch(me.position) {
62288                 case "left":
62289                     x = insets;
62290                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62291                     break;
62292                 case "right":
62293                     x = mfloor(surface.width - legendWidth) - insets;
62294                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62295                     break;
62296                 case "top":
62297                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62298                     y = insets;
62299                     break;
62300                 case "bottom":
62301                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62302                     y = mfloor(surface.height - legendHeight) - insets;
62303                     break;
62304                 default:
62305                     x = mfloor(me.origX) + insets;
62306                     y = mfloor(me.origY) + insets;
62307             }
62308             me.x = x;
62309             me.y = y;
62310
62311             // Update the position of each item
62312             Ext.each(me.items, function(item) {
62313                 item.updatePosition();
62314             });
62315             // Update the position of the outer box
62316             me.boxSprite.setAttributes(me.getBBox(), true);
62317         }
62318     }
62319 });
62320
62321 /**
62322  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
62323  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
62324  * updates its display whenever data in the Store changes. In addition, the look and feel
62325  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
62326  * 
62327  * ## Creating a Simple Chart
62328  * 
62329  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
62330  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
62331  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
62332  * 
62333  * ### 1. Creating a Store
62334  * 
62335  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
62336  * data that will be displayed in the Chart. For example the data for a chart that displays
62337  * a weather forecast could be represented as a series of "WeatherPoint" data points with
62338  * two fields - "temperature", and "date":
62339  * 
62340  *     Ext.define('WeatherPoint', {
62341  *         extend: 'Ext.data.Model',
62342  *         fields: ['temperature', 'date']
62343  *     });
62344  * 
62345  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
62346  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
62347  * 
62348  *     var store = Ext.create('Ext.data.Store', {
62349  *         model: 'WeatherPoint',
62350  *         data: [
62351  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
62352  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
62353  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
62354  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
62355  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
62356  *         ]
62357  *     });
62358  *    
62359  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
62360  * 
62361  * ### 2. Creating the Chart object
62362  * 
62363  * Now that a Store has been created it can be used in a Chart:
62364  * 
62365  *     Ext.create('Ext.chart.Chart', {
62366  *        renderTo: Ext.getBody(),
62367  *        width: 400,
62368  *        height: 300,
62369  *        store: store
62370  *     });
62371  *    
62372  * That's all it takes to create a Chart instance that is backed by a Store.
62373  * However, if the above code is run in a browser, a blank screen will be displayed.
62374  * This is because the two pieces that are responsible for the visual display,
62375  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
62376  * 
62377  * ### 3. Configuring the Axes
62378  * 
62379  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
62380  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
62381  * 
62382  *     Ext.create('Ext.chart.Chart', {
62383  *         ...
62384  *         axes: [
62385  *             {
62386  *                 title: 'Temperature',
62387  *                 type: 'Numeric',
62388  *                 position: 'left',
62389  *                 fields: ['temperature'],
62390  *                 minimum: 0,
62391  *                 maximum: 100
62392  *             },
62393  *             {
62394  *                 title: 'Time',
62395  *                 type: 'Time',
62396  *                 position: 'bottom',
62397  *                 fields: ['date'],
62398  *                 dateFormat: 'ga'
62399  *             }
62400  *         ]
62401  *     });
62402  *    
62403  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
62404  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
62405  * defined above. The minimum value for this axis is "0", and the maximum is "100".
62406  * 
62407  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
62408  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
62409  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
62410  * configuration tells the Time Axis how to format it's labels.
62411  * 
62412  * Here's what the Chart looks like now that it has its Axes configured:
62413  * 
62414  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
62415  * 
62416  * ### 4. Configuring the Series
62417  * 
62418  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
62419  * Series are responsible for the visual representation of the data points contained in the Store.
62420  * This example only has one Series:
62421  * 
62422  *     Ext.create('Ext.chart.Chart', {
62423  *         ...
62424  *         axes: [
62425  *             ...
62426  *         ],
62427  *         series: [
62428  *             {
62429  *                 type: 'line',
62430  *                 xField: 'date',
62431  *                 yField: 'temperature'
62432  *             }
62433  *         ]
62434  *     });
62435  *     
62436  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
62437  * from the "WeatherPoint" Models in the Store to plot its data points:
62438  * 
62439  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
62440  * 
62441  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
62442  * 
62443  * ## Themes
62444  * 
62445  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
62446  * 
62447  *     Ext.create('Ext.chart.Chart', {
62448  *         ...
62449  *         theme: 'Green',
62450  *         ...
62451  *     });
62452  * 
62453  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
62454  * 
62455  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
62456  * 
62457  */
62458 Ext.define('Ext.chart.Chart', {
62459
62460     /* Begin Definitions */
62461
62462     alias: 'widget.chart',
62463
62464     extend: 'Ext.draw.Component',
62465     
62466     mixins: {
62467         themeManager: 'Ext.chart.theme.Theme',
62468         mask: 'Ext.chart.Mask',
62469         navigation: 'Ext.chart.Navigation'
62470     },
62471
62472     requires: [
62473         'Ext.util.MixedCollection',
62474         'Ext.data.StoreManager',
62475         'Ext.chart.Legend',
62476         'Ext.util.DelayedTask'
62477     ],
62478
62479     /* End Definitions */
62480
62481     // @private
62482     viewBox: false,
62483
62484     /**
62485      * @cfg {String} theme
62486      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
62487      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
62488      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
62489      * is 'Base'.
62490      */
62491
62492     /**
62493      * @cfg {Boolean/Object} animate
62494      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
62495      * object to be used for default chart animations. Defaults to false.
62496      */
62497     animate: false,
62498
62499     /**
62500      * @cfg {Boolean/Object} legend
62501      * True for the default legend display or a legend config object. Defaults to false.
62502      */
62503     legend: false,
62504
62505     /**
62506      * @cfg {Number} insetPadding
62507      * The amount of inset padding in pixels for the chart. Defaults to 10.
62508      */
62509     insetPadding: 10,
62510
62511     /**
62512      * @cfg {String[]} enginePriority
62513      * Defines the priority order for which Surface implementation to use. The first one supported by the current
62514      * environment will be used. Defaults to `['Svg', 'Vml']`.
62515      */
62516     enginePriority: ['Svg', 'Vml'],
62517
62518     /**
62519      * @cfg {Object/Boolean} background
62520      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
62521      * background. For example, if `background` were to be a color we could set the object as
62522      *
62523      *     background: {
62524      *         //color string
62525      *         fill: '#ccc'
62526      *     }
62527      *
62528      * You can specify an image by using:
62529      *
62530      *     background: {
62531      *         image: 'http://path.to.image/'
62532      *     }
62533      *
62534      * Also you can specify a gradient by using the gradient object syntax:
62535      *
62536      *     background: {
62537      *         gradient: {
62538      *             id: 'gradientId',
62539      *             angle: 45,
62540      *             stops: {
62541      *                 0: {
62542      *                     color: '#555'
62543      *                 }
62544      *                 100: {
62545      *                     color: '#ddd'
62546      *                 }
62547      *             }
62548      *         }
62549      *     }
62550      */
62551     background: false,
62552
62553     /**
62554      * @cfg {Object[]} gradients
62555      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
62556      * array of objects with the following properties:
62557      *
62558      * - **id** - string - The unique name of the gradient.
62559      * - **angle** - number, optional - The angle of the gradient in degrees.
62560      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
62561      *
62562      * For example:
62563      *
62564      *     gradients: [{
62565      *         id: 'gradientId',
62566      *         angle: 45,
62567      *         stops: {
62568      *             0: {
62569      *                 color: '#555'
62570      *             },
62571      *             100: {
62572      *                 color: '#ddd'
62573      *             }
62574      *         }
62575      *     }, {
62576      *         id: 'gradientId2',
62577      *         angle: 0,
62578      *         stops: {
62579      *             0: {
62580      *                 color: '#590'
62581      *             },
62582      *             20: {
62583      *                 color: '#599'
62584      *             },
62585      *             100: {
62586      *                 color: '#ddd'
62587      *             }
62588      *         }
62589      *     }]
62590      *
62591      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
62592      *
62593      *     sprite.setAttributes({
62594      *         fill: 'url(#gradientId)'
62595      *     }, true);
62596      */
62597
62598     /**
62599      * @cfg {Ext.data.Store} store
62600      * The store that supplies data to this chart.
62601      */
62602
62603     /**
62604      * @cfg {Ext.chart.series.Series[]} series
62605      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
62606      * 
62607      *     series: [{
62608      *         type: 'column',
62609      *         axis: 'left',
62610      *         listeners: {
62611      *             'afterrender': function() {
62612      *                 console('afterrender');
62613      *             }
62614      *         },
62615      *         xField: 'category',
62616      *         yField: 'data1'
62617      *     }]
62618      */
62619
62620     /**
62621      * @cfg {Ext.chart.axis.Axis[]} axes
62622      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
62623      * 
62624      *     axes: [{
62625      *         type: 'Numeric',
62626      *         position: 'left',
62627      *         fields: ['data1'],
62628      *         title: 'Number of Hits',
62629      *         minimum: 0,
62630      *         //one minor tick between two major ticks
62631      *         minorTickSteps: 1
62632      *     }, {
62633      *         type: 'Category',
62634      *         position: 'bottom',
62635      *         fields: ['name'],
62636      *         title: 'Month of the Year'
62637      *     }]
62638      */
62639
62640     constructor: function(config) {
62641         var me = this,
62642             defaultAnim;
62643             
62644         config = Ext.apply({}, config);
62645         me.initTheme(config.theme || me.theme);
62646         if (me.gradients) {
62647             Ext.apply(config, { gradients: me.gradients });
62648         }
62649         if (me.background) {
62650             Ext.apply(config, { background: me.background });
62651         }
62652         if (config.animate) {
62653             defaultAnim = {
62654                 easing: 'ease',
62655                 duration: 500
62656             };
62657             if (Ext.isObject(config.animate)) {
62658                 config.animate = Ext.applyIf(config.animate, defaultAnim);
62659             }
62660             else {
62661                 config.animate = defaultAnim;
62662             }
62663         }
62664         me.mixins.mask.constructor.call(me, config);
62665         me.mixins.navigation.constructor.call(me, config);
62666         me.callParent([config]);
62667     },
62668     
62669     getChartStore: function(){
62670         return this.substore || this.store;    
62671     },
62672
62673     initComponent: function() {
62674         var me = this,
62675             axes,
62676             series;
62677         me.callParent();
62678         me.addEvents(
62679             'itemmousedown',
62680             'itemmouseup',
62681             'itemmouseover',
62682             'itemmouseout',
62683             'itemclick',
62684             'itemdoubleclick',
62685             'itemdragstart',
62686             'itemdrag',
62687             'itemdragend',
62688             /**
62689              * @event beforerefresh
62690              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
62691              * {@link #refresh} action will be cancelled.
62692              * @param {Ext.chart.Chart} this
62693              */
62694             'beforerefresh',
62695             /**
62696              * @event refresh
62697              * Fires after the chart data has been refreshed.
62698              * @param {Ext.chart.Chart} this
62699              */
62700             'refresh'
62701         );
62702         Ext.applyIf(me, {
62703             zoom: {
62704                 width: 1,
62705                 height: 1,
62706                 x: 0,
62707                 y: 0
62708             }
62709         });
62710         me.maxGutter = [0, 0];
62711         me.store = Ext.data.StoreManager.lookup(me.store);
62712         axes = me.axes;
62713         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
62714         if (axes) {
62715             me.axes.addAll(axes);
62716         }
62717         series = me.series;
62718         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
62719         if (series) {
62720             me.series.addAll(series);
62721         }
62722         if (me.legend !== false) {
62723             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
62724         }
62725
62726         me.on({
62727             mousemove: me.onMouseMove,
62728             mouseleave: me.onMouseLeave,
62729             mousedown: me.onMouseDown,
62730             mouseup: me.onMouseUp,
62731             scope: me
62732         });
62733     },
62734
62735     // @private overrides the component method to set the correct dimensions to the chart.
62736     afterComponentLayout: function(width, height) {
62737         var me = this;
62738         if (Ext.isNumber(width) && Ext.isNumber(height)) {
62739             me.curWidth = width;
62740             me.curHeight = height;
62741             me.redraw(true);
62742         }
62743         this.callParent(arguments);
62744     },
62745
62746     /**
62747      * Redraws the chart. If animations are set this will animate the chart too. 
62748      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
62749      */
62750     redraw: function(resize) {
62751         var me = this,
62752             chartBBox = me.chartBBox = {
62753                 x: 0,
62754                 y: 0,
62755                 height: me.curHeight,
62756                 width: me.curWidth
62757             },
62758             legend = me.legend;
62759         me.surface.setSize(chartBBox.width, chartBBox.height);
62760         // Instantiate Series and Axes
62761         me.series.each(me.initializeSeries, me);
62762         me.axes.each(me.initializeAxis, me);
62763         //process all views (aggregated data etc) on stores
62764         //before rendering.
62765         me.axes.each(function(axis) {
62766             axis.processView();
62767         });
62768         me.axes.each(function(axis) {
62769             axis.drawAxis(true);
62770         });
62771
62772         // Create legend if not already created
62773         if (legend !== false) {
62774             legend.create();
62775         }
62776
62777         // Place axes properly, including influence from each other
62778         me.alignAxes();
62779
62780         // Reposition legend based on new axis alignment
62781         if (me.legend !== false) {
62782             legend.updatePosition();
62783         }
62784
62785         // Find the max gutter
62786         me.getMaxGutter();
62787
62788         // Draw axes and series
62789         me.resizing = !!resize;
62790
62791         me.axes.each(me.drawAxis, me);
62792         me.series.each(me.drawCharts, me);
62793         me.resizing = false;
62794     },
62795
62796     // @private set the store after rendering the chart.
62797     afterRender: function() {
62798         var ref,
62799             me = this;
62800         this.callParent();
62801
62802         if (me.categoryNames) {
62803             me.setCategoryNames(me.categoryNames);
62804         }
62805
62806         if (me.tipRenderer) {
62807             ref = me.getFunctionRef(me.tipRenderer);
62808             me.setTipRenderer(ref.fn, ref.scope);
62809         }
62810         me.bindStore(me.store, true);
62811         me.refresh();
62812     },
62813
62814     // @private get x and y position of the mouse cursor.
62815     getEventXY: function(e) {
62816         var me = this,
62817             box = this.surface.getRegion(),
62818             pageXY = e.getXY(),
62819             x = pageXY[0] - box.left,
62820             y = pageXY[1] - box.top;
62821         return [x, y];
62822     },
62823
62824     // @private wrap the mouse down position to delegate the event to the series.
62825     onClick: function(e) {
62826         var me = this,
62827             position = me.getEventXY(e),
62828             item;
62829
62830         // Ask each series if it has an item corresponding to (not necessarily exactly
62831         // on top of) the current mouse coords. Fire itemclick event.
62832         me.series.each(function(series) {
62833             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62834                 if (series.getItemForPoint) {
62835                     item = series.getItemForPoint(position[0], position[1]);
62836                     if (item) {
62837                         series.fireEvent('itemclick', item);
62838                     }
62839                 }
62840             }
62841         }, me);
62842     },
62843
62844     // @private wrap the mouse down position to delegate the event to the series.
62845     onMouseDown: function(e) {
62846         var me = this,
62847             position = me.getEventXY(e),
62848             item;
62849
62850         if (me.mask) {
62851             me.mixins.mask.onMouseDown.call(me, e);
62852         }
62853         // Ask each series if it has an item corresponding to (not necessarily exactly
62854         // on top of) the current mouse coords. Fire mousedown event.
62855         me.series.each(function(series) {
62856             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62857                 if (series.getItemForPoint) {
62858                     item = series.getItemForPoint(position[0], position[1]);
62859                     if (item) {
62860                         series.fireEvent('itemmousedown', item);
62861                     }
62862                 }
62863             }
62864         }, me);
62865     },
62866
62867     // @private wrap the mouse up event to delegate it to the series.
62868     onMouseUp: function(e) {
62869         var me = this,
62870             position = me.getEventXY(e),
62871             item;
62872
62873         if (me.mask) {
62874             me.mixins.mask.onMouseUp.call(me, e);
62875         }
62876         // Ask each series if it has an item corresponding to (not necessarily exactly
62877         // on top of) the current mouse coords. Fire mousedown event.
62878         me.series.each(function(series) {
62879             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62880                 if (series.getItemForPoint) {
62881                     item = series.getItemForPoint(position[0], position[1]);
62882                     if (item) {
62883                         series.fireEvent('itemmouseup', item);
62884                     }
62885                 }
62886             }
62887         }, me);
62888     },
62889
62890     // @private wrap the mouse move event so it can be delegated to the series.
62891     onMouseMove: function(e) {
62892         var me = this,
62893             position = me.getEventXY(e),
62894             item, last, storeItem, storeField;
62895
62896         if (me.mask) {
62897             me.mixins.mask.onMouseMove.call(me, e);
62898         }
62899         // Ask each series if it has an item corresponding to (not necessarily exactly
62900         // on top of) the current mouse coords. Fire itemmouseover/out events.
62901         me.series.each(function(series) {
62902             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62903                 if (series.getItemForPoint) {
62904                     item = series.getItemForPoint(position[0], position[1]);
62905                     last = series._lastItemForPoint;
62906                     storeItem = series._lastStoreItem;
62907                     storeField = series._lastStoreField;
62908
62909
62910                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
62911                         if (last) {
62912                             series.fireEvent('itemmouseout', last);
62913                             delete series._lastItemForPoint;
62914                             delete series._lastStoreField;
62915                             delete series._lastStoreItem;
62916                         }
62917                         if (item) {
62918                             series.fireEvent('itemmouseover', item);
62919                             series._lastItemForPoint = item;
62920                             series._lastStoreItem = item.storeItem;
62921                             series._lastStoreField = item.storeField;
62922                         }
62923                     }
62924                 }
62925             } else {
62926                 last = series._lastItemForPoint;
62927                 if (last) {
62928                     series.fireEvent('itemmouseout', last);
62929                     delete series._lastItemForPoint;
62930                     delete series._lastStoreField;
62931                     delete series._lastStoreItem;
62932                 }
62933             }
62934         }, me);
62935     },
62936
62937     // @private handle mouse leave event.
62938     onMouseLeave: function(e) {
62939         var me = this;
62940         if (me.mask) {
62941             me.mixins.mask.onMouseLeave.call(me, e);
62942         }
62943         me.series.each(function(series) {
62944             delete series._lastItemForPoint;
62945         });
62946     },
62947
62948     // @private buffered refresh for when we update the store
62949     delayRefresh: function() {
62950         var me = this;
62951         if (!me.refreshTask) {
62952             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
62953         }
62954         me.refreshTask.delay(me.refreshBuffer);
62955     },
62956
62957     // @private
62958     refresh: function() {
62959         var me = this;
62960         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
62961             if (me.fireEvent('beforerefresh', me) !== false) {
62962                 me.redraw();
62963                 me.fireEvent('refresh', me);
62964             }
62965         }
62966     },
62967
62968     /**
62969      * Changes the data store bound to this chart and refreshes it.
62970      * @param {Ext.data.Store} store The store to bind to this chart
62971      */
62972     bindStore: function(store, initial) {
62973         var me = this;
62974         if (!initial && me.store) {
62975             if (store !== me.store && me.store.autoDestroy) {
62976                 me.store.destroyStore();
62977             }
62978             else {
62979                 me.store.un('datachanged', me.refresh, me);
62980                 me.store.un('add', me.delayRefresh, me);
62981                 me.store.un('remove', me.delayRefresh, me);
62982                 me.store.un('update', me.delayRefresh, me);
62983                 me.store.un('clear', me.refresh, me);
62984             }
62985         }
62986         if (store) {
62987             store = Ext.data.StoreManager.lookup(store);
62988             store.on({
62989                 scope: me,
62990                 datachanged: me.refresh,
62991                 add: me.delayRefresh,
62992                 remove: me.delayRefresh,
62993                 update: me.delayRefresh,
62994                 clear: me.refresh
62995             });
62996         }
62997         me.store = store;
62998         if (store && !initial) {
62999             me.refresh();
63000         }
63001     },
63002
63003     // @private Create Axis
63004     initializeAxis: function(axis) {
63005         var me = this,
63006             chartBBox = me.chartBBox,
63007             w = chartBBox.width,
63008             h = chartBBox.height,
63009             x = chartBBox.x,
63010             y = chartBBox.y,
63011             themeAttrs = me.themeAttrs,
63012             config = {
63013                 chart: me
63014             };
63015         if (themeAttrs) {
63016             config.axisStyle = Ext.apply({}, themeAttrs.axis);
63017             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
63018             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
63019             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
63020             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
63021             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
63022             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
63023             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
63024             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
63025         }
63026         switch (axis.position) {
63027             case 'top':
63028                 Ext.apply(config, {
63029                     length: w,
63030                     width: h,
63031                     x: x,
63032                     y: y
63033                 });
63034             break;
63035             case 'bottom':
63036                 Ext.apply(config, {
63037                     length: w,
63038                     width: h,
63039                     x: x,
63040                     y: h
63041                 });
63042             break;
63043             case 'left':
63044                 Ext.apply(config, {
63045                     length: h,
63046                     width: w,
63047                     x: x,
63048                     y: h
63049                 });
63050             break;
63051             case 'right':
63052                 Ext.apply(config, {
63053                     length: h,
63054                     width: w,
63055                     x: w,
63056                     y: h
63057                 });
63058             break;
63059         }
63060         if (!axis.chart) {
63061             Ext.apply(config, axis);
63062             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
63063         }
63064         else {
63065             Ext.apply(axis, config);
63066         }
63067     },
63068
63069
63070     /**
63071      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
63072      * for the space taken up on each side by the axes and legend.
63073      */
63074     alignAxes: function() {
63075         var me = this,
63076             axes = me.axes,
63077             legend = me.legend,
63078             edges = ['top', 'right', 'bottom', 'left'],
63079             chartBBox,
63080             insetPadding = me.insetPadding,
63081             insets = {
63082                 top: insetPadding,
63083                 right: insetPadding,
63084                 bottom: insetPadding,
63085                 left: insetPadding
63086             };
63087
63088         function getAxis(edge) {
63089             var i = axes.findIndex('position', edge);
63090             return (i < 0) ? null : axes.getAt(i);
63091         }
63092
63093         // Find the space needed by axes and legend as a positive inset from each edge
63094         Ext.each(edges, function(edge) {
63095             var isVertical = (edge === 'left' || edge === 'right'),
63096                 axis = getAxis(edge),
63097                 bbox;
63098
63099             // Add legend size if it's on this edge
63100             if (legend !== false) {
63101                 if (legend.position === edge) {
63102                     bbox = legend.getBBox();
63103                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
63104                 }
63105             }
63106
63107             // Add axis size if there's one on this edge only if it has been
63108             //drawn before.
63109             if (axis && axis.bbox) {
63110                 bbox = axis.bbox;
63111                 insets[edge] += (isVertical ? bbox.width : bbox.height);
63112             }
63113         });
63114         // Build the chart bbox based on the collected inset values
63115         chartBBox = {
63116             x: insets.left,
63117             y: insets.top,
63118             width: me.curWidth - insets.left - insets.right,
63119             height: me.curHeight - insets.top - insets.bottom
63120         };
63121         me.chartBBox = chartBBox;
63122
63123         // Go back through each axis and set its length and position based on the
63124         // corresponding edge of the chartBBox
63125         axes.each(function(axis) {
63126             var pos = axis.position,
63127                 isVertical = (pos === 'left' || pos === 'right');
63128
63129             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
63130             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
63131             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
63132             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
63133         });
63134     },
63135
63136     // @private initialize the series.
63137     initializeSeries: function(series, idx) {
63138         var me = this,
63139             themeAttrs = me.themeAttrs,
63140             seriesObj, markerObj, seriesThemes, st,
63141             markerThemes, colorArrayStyle = [],
63142             i = 0, l,
63143             config = {
63144                 chart: me,
63145                 seriesId: series.seriesId
63146             };
63147         if (themeAttrs) {
63148             seriesThemes = themeAttrs.seriesThemes;
63149             markerThemes = themeAttrs.markerThemes;
63150             seriesObj = Ext.apply({}, themeAttrs.series);
63151             markerObj = Ext.apply({}, themeAttrs.marker);
63152             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
63153             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
63154             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
63155             if (themeAttrs.colors) {
63156                 config.colorArrayStyle = themeAttrs.colors;
63157             } else {
63158                 colorArrayStyle = [];
63159                 for (l = seriesThemes.length; i < l; i++) {
63160                     st = seriesThemes[i];
63161                     if (st.fill || st.stroke) {
63162                         colorArrayStyle.push(st.fill || st.stroke);
63163                     }
63164                 }
63165                 if (colorArrayStyle.length) {
63166                     config.colorArrayStyle = colorArrayStyle;
63167                 }
63168             }
63169             config.seriesIdx = idx;
63170         }
63171         if (series instanceof Ext.chart.series.Series) {
63172             Ext.apply(series, config);
63173         } else {
63174             Ext.applyIf(config, series);
63175             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
63176         }
63177         if (series.initialize) {
63178             series.initialize();
63179         }
63180     },
63181
63182     // @private
63183     getMaxGutter: function() {
63184         var me = this,
63185             maxGutter = [0, 0];
63186         me.series.each(function(s) {
63187             var gutter = s.getGutters && s.getGutters() || [0, 0];
63188             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
63189             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
63190         });
63191         me.maxGutter = maxGutter;
63192     },
63193
63194     // @private draw axis.
63195     drawAxis: function(axis) {
63196         axis.drawAxis();
63197     },
63198
63199     // @private draw series.
63200     drawCharts: function(series) {
63201         series.triggerafterrender = false;
63202         series.drawSeries();
63203         if (!this.animate) {
63204             series.fireEvent('afterrender');
63205         }
63206     },
63207
63208     // @private remove gently.
63209     destroy: function() {
63210         Ext.destroy(this.surface);
63211         this.bindStore(null);
63212         this.callParent(arguments);
63213     }
63214 });
63215
63216 /**
63217  * @class Ext.chart.Highlight
63218  * A mixin providing highlight functionality for Ext.chart.series.Series.
63219  */
63220 Ext.define('Ext.chart.Highlight', {
63221
63222     /* Begin Definitions */
63223
63224     requires: ['Ext.fx.Anim'],
63225
63226     /* End Definitions */
63227
63228     /**
63229      * Highlight the given series item.
63230      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
63231      * or just use default styles per series by setting highlight = true.
63232      */
63233     highlight: false,
63234
63235     highlightCfg : null,
63236
63237     constructor: function(config) {
63238         if (config.highlight) {
63239             if (config.highlight !== true) { //is an object
63240                 this.highlightCfg = Ext.apply({}, config.highlight);
63241             }
63242             else {
63243                 this.highlightCfg = {
63244                     fill: '#fdd',
63245                     radius: 20,
63246                     lineWidth: 5,
63247                     stroke: '#f55'
63248                 };
63249             }
63250         }
63251     },
63252
63253     /**
63254      * Highlight the given series item.
63255      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
63256      */
63257     highlightItem: function(item) {
63258         if (!item) {
63259             return;
63260         }
63261         
63262         var me = this,
63263             sprite = item.sprite,
63264             opts = me.highlightCfg,
63265             surface = me.chart.surface,
63266             animate = me.chart.animate,
63267             p, from, to, pi;
63268
63269         if (!me.highlight || !sprite || sprite._highlighted) {
63270             return;
63271         }
63272         if (sprite._anim) {
63273             sprite._anim.paused = true;
63274         }
63275         sprite._highlighted = true;
63276         if (!sprite._defaults) {
63277             sprite._defaults = Ext.apply({}, sprite.attr);
63278             from = {};
63279             to = {};
63280             for (p in opts) {
63281                 if (! (p in sprite._defaults)) {
63282                     sprite._defaults[p] = surface.availableAttrs[p];
63283                 }
63284                 from[p] = sprite._defaults[p];
63285                 to[p] = opts[p];
63286                 if (Ext.isObject(opts[p])) {
63287                     from[p] = {};
63288                     to[p] = {};
63289                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
63290                     Ext.apply(from[p], sprite._defaults[p]);
63291                     for (pi in sprite._defaults[p]) {
63292                         if (! (pi in opts[p])) {
63293                             to[p][pi] = from[p][pi];
63294                         } else {
63295                             to[p][pi] = opts[p][pi];
63296                         }
63297                     }
63298                     for (pi in opts[p]) {
63299                         if (! (pi in to[p])) {
63300                             to[p][pi] = opts[p][pi];
63301                         }
63302                     }
63303                 }
63304             }
63305             sprite._from = from;
63306             sprite._to = to;
63307             sprite._endStyle = to;
63308         }
63309         if (animate) {
63310             sprite._anim = Ext.create('Ext.fx.Anim', {
63311                 target: sprite,
63312                 from: sprite._from,
63313                 to: sprite._to,
63314                 duration: 150
63315             });
63316         } else {
63317             sprite.setAttributes(sprite._to, true);
63318         }
63319     },
63320
63321     /**
63322      * Un-highlight any existing highlights
63323      */
63324     unHighlightItem: function() {
63325         if (!this.highlight || !this.items) {
63326             return;
63327         }
63328
63329         var me = this,
63330             items = me.items,
63331             len = items.length,
63332             opts = me.highlightCfg,
63333             animate = me.chart.animate,
63334             i = 0,
63335             obj, p, sprite;
63336
63337         for (; i < len; i++) {
63338             if (!items[i]) {
63339                 continue;
63340             }
63341             sprite = items[i].sprite;
63342             if (sprite && sprite._highlighted) {
63343                 if (sprite._anim) {
63344                     sprite._anim.paused = true;
63345                 }
63346                 obj = {};
63347                 for (p in opts) {
63348                     if (Ext.isObject(sprite._defaults[p])) {
63349                         obj[p] = {};
63350                         Ext.apply(obj[p], sprite._defaults[p]);
63351                     }
63352                     else {
63353                         obj[p] = sprite._defaults[p];
63354                     }
63355                 }
63356                 if (animate) {
63357                     //sprite._to = obj;
63358                     sprite._endStyle = obj;
63359                     sprite._anim = Ext.create('Ext.fx.Anim', {
63360                         target: sprite,
63361                         to: obj,
63362                         duration: 150
63363                     });
63364                 }
63365                 else {
63366                     sprite.setAttributes(obj, true);
63367                 }
63368                 delete sprite._highlighted;
63369                 //delete sprite._defaults;
63370             }
63371         }
63372     },
63373
63374     cleanHighlights: function() {
63375         if (!this.highlight) {
63376             return;
63377         }
63378
63379         var group = this.group,
63380             markerGroup = this.markerGroup,
63381             i = 0,
63382             l;
63383         for (l = group.getCount(); i < l; i++) {
63384             delete group.getAt(i)._defaults;
63385         }
63386         if (markerGroup) {
63387             for (l = markerGroup.getCount(); i < l; i++) {
63388                 delete markerGroup.getAt(i)._defaults;
63389             }
63390         }
63391     }
63392 });
63393 /**
63394  * @class Ext.chart.Label
63395  *
63396  * Labels is a mixin to the Series class. Labels methods are implemented
63397  * in each of the Series (Pie, Bar, etc) for label creation and placement.
63398  *
63399  * The methods implemented by the Series are:
63400  *
63401  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
63402  *   The arguments of the method are:
63403  *   - *`storeItem`* The element of the store that is related to the label sprite.
63404  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63405  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63406  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
63407  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
63408  *
63409  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
63410  *    The arguments of the method are:
63411  *    - *`label`* The sprite label.</li>
63412  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
63413  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63414  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63415  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
63416  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
63417  *    - *`animate`* A boolean value to set or unset animations for the labels.
63418  */
63419 Ext.define('Ext.chart.Label', {
63420
63421     /* Begin Definitions */
63422
63423     requires: ['Ext.draw.Color'],
63424
63425     /* End Definitions */
63426
63427     /**
63428      * @cfg {Object} label
63429      * Object with the following properties:
63430      *
63431      * - **display** : String
63432      *
63433      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
63434      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
63435      *   Default value: 'none'.
63436      *
63437      * - **color** : String
63438      *
63439      *   The color of the label text.
63440      *   Default value: '#000' (black).
63441      *
63442      * - **contrast** : Boolean
63443      *
63444      *   True to render the label in contrasting color with the backround.
63445      *   Default value: false.
63446      *
63447      * - **field** : String
63448      *
63449      *   The name of the field to be displayed in the label.
63450      *   Default value: 'name'.
63451      *
63452      * - **minMargin** : Number
63453      *
63454      *   Specifies the minimum distance from a label to the origin of the visualization.
63455      *   This parameter is useful when using PieSeries width variable pie slice lengths.
63456      *   Default value: 50.
63457      *
63458      * - **font** : String
63459      *
63460      *   The font used for the labels.
63461      *   Default value: "11px Helvetica, sans-serif".
63462      *
63463      * - **orientation** : String
63464      *
63465      *   Either "horizontal" or "vertical".
63466      *   Dafault value: "horizontal".
63467      *
63468      * - **renderer** : Function
63469      *
63470      *   Optional function for formatting the label into a displayable value.
63471      *   Default value: function(v) { return v; }
63472      */
63473
63474     //@private a regex to parse url type colors.
63475     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
63476
63477     //@private the mixin constructor. Used internally by Series.
63478     constructor: function(config) {
63479         var me = this;
63480         me.label = Ext.applyIf(me.label || {},
63481         {
63482             display: "none",
63483             color: "#000",
63484             field: "name",
63485             minMargin: 50,
63486             font: "11px Helvetica, sans-serif",
63487             orientation: "horizontal",
63488             renderer: function(v) {
63489                 return v;
63490             }
63491         });
63492
63493         if (me.label.display !== 'none') {
63494             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
63495         }
63496     },
63497
63498     //@private a method to render all labels in the labelGroup
63499     renderLabels: function() {
63500         var me = this,
63501             chart = me.chart,
63502             gradients = chart.gradients,
63503             items = me.items,
63504             animate = chart.animate,
63505             config = me.label,
63506             display = config.display,
63507             color = config.color,
63508             field = [].concat(config.field),
63509             group = me.labelsGroup,
63510             groupLength = (group || 0) && group.length,
63511             store = me.chart.store,
63512             len = store.getCount(),
63513             itemLength = (items || 0) && items.length,
63514             ratio = itemLength / len,
63515             gradientsCount = (gradients || 0) && gradients.length,
63516             Color = Ext.draw.Color,
63517             hides = [],
63518             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
63519             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
63520
63521         if (display == 'none') {
63522             return;
63523         }
63524         // no items displayed, hide all labels
63525         if(itemLength == 0){
63526             while(groupLength--)
63527                 hides.push(groupLength);
63528         }else{
63529             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
63530                 index = 0;
63531                 for (j = 0; j < ratio; j++) {
63532                     item = items[count];
63533                     label = group.getAt(groupIndex);
63534                     storeItem = store.getAt(i);
63535                     //check the excludes
63536                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
63537                         if(field[j]){
63538                             hides.push(groupIndex);
63539                         }
63540                         index++;
63541
63542                     }
63543
63544                     if (!item && label) {
63545                         label.hide(true);
63546                         groupIndex++;
63547                     }
63548
63549                     if (item && field[j]) {
63550                         if (!label) {
63551                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
63552                         }
63553                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
63554                         groupIndex++;
63555
63556                         //set contrast
63557                         if (config.contrast && item.sprite) {
63558                             sprite = item.sprite;
63559                             //set the color string to the color to be set.
63560                             if (sprite._endStyle) {
63561                                 colorString = sprite._endStyle.fill;
63562                             }
63563                             else if (sprite._to) {
63564                                 colorString = sprite._to.fill;
63565                             }
63566                             else {
63567                                 colorString = sprite.attr.fill;
63568                             }
63569                             colorString = colorString || sprite.attr.fill;
63570
63571                             spriteColor = Color.fromString(colorString);
63572                             //color wasn't parsed property maybe because it's a gradient id
63573                             if (colorString && !spriteColor) {
63574                                 colorString = colorString.match(me.colorStringRe)[1];
63575                                 for (k = 0; k < gradientsCount; k++) {
63576                                     gradient = gradients[k];
63577                                     if (gradient.id == colorString) {
63578                                         //avg color stops
63579                                         colorStop = 0; colorStopTotal = 0;
63580                                         for (colorStopIndex in gradient.stops) {
63581                                             colorStop++;
63582                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
63583                                         }
63584                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
63585                                         break;
63586                                     }
63587                                 }
63588                             }
63589                             else {
63590                                 spriteBrightness = spriteColor.getGrayscale() / 255;
63591                             }
63592                             if (label.isOutside) {
63593                                 spriteBrightness = 1;
63594                             }
63595                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
63596                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
63597                             label.setAttributes({
63598                                 fill: String(Color.fromHSL.apply({}, labelColor))
63599                             }, true);
63600                         }
63601
63602                     }
63603                     count++;
63604                     index++;
63605                 }
63606             }
63607         }
63608         me.hideLabels(hides);
63609     },
63610     hideLabels: function(hides){
63611         var labelsGroup = this.labelsGroup,
63612             hlen = hides.length;
63613         while(hlen--)
63614             labelsGroup.getAt(hides[hlen]).hide(true);
63615     }
63616 });
63617 Ext.define('Ext.chart.MaskLayer', {
63618     extend: 'Ext.Component',
63619     
63620     constructor: function(config) {
63621         config = Ext.apply(config || {}, {
63622             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
63623         });
63624         this.callParent([config]);    
63625     },
63626     
63627     initComponent: function() {
63628         var me = this;
63629         me.callParent(arguments);
63630         me.addEvents(
63631             'mousedown',
63632             'mouseup',
63633             'mousemove',
63634             'mouseenter',
63635             'mouseleave'
63636         );
63637     },
63638
63639     initDraggable: function() {
63640         this.callParent(arguments);
63641         this.dd.onStart = function (e) {
63642             var me = this,
63643                 comp = me.comp;
63644     
63645             // Cache the start [X, Y] array
63646             this.startPosition = comp.getPosition(true);
63647     
63648             // If client Component has a ghost method to show a lightweight version of itself
63649             // then use that as a drag proxy unless configured to liveDrag.
63650             if (comp.ghost && !comp.liveDrag) {
63651                  me.proxy = comp.ghost();
63652                  me.dragTarget = me.proxy.header.el;
63653             }
63654     
63655             // Set the constrainTo Region before we start dragging.
63656             if (me.constrain || me.constrainDelegate) {
63657                 me.constrainTo = me.calculateConstrainRegion();
63658             }
63659         };
63660     }
63661 });
63662 /**
63663  * @class Ext.chart.TipSurface
63664  * @ignore
63665  */
63666 Ext.define('Ext.chart.TipSurface', {
63667
63668     /* Begin Definitions */
63669
63670     extend: 'Ext.draw.Component',
63671
63672     /* End Definitions */
63673
63674     spriteArray: false,
63675     renderFirst: true,
63676
63677     constructor: function(config) {
63678         this.callParent([config]);
63679         if (config.sprites) {
63680             this.spriteArray = [].concat(config.sprites);
63681             delete config.sprites;
63682         }
63683     },
63684
63685     onRender: function() {
63686         var me = this,
63687             i = 0,
63688             l = 0,
63689             sp,
63690             sprites;
63691             this.callParent(arguments);
63692         sprites = me.spriteArray;
63693         if (me.renderFirst && sprites) {
63694             me.renderFirst = false;
63695             for (l = sprites.length; i < l; i++) {
63696                 sp = me.surface.add(sprites[i]);
63697                 sp.setAttributes({
63698                     hidden: false
63699                 },
63700                 true);
63701             }
63702         }
63703     }
63704 });
63705
63706 /**
63707  * @class Ext.chart.Tip
63708  * Provides tips for Ext.chart.series.Series.
63709  */
63710 Ext.define('Ext.chart.Tip', {
63711
63712     /* Begin Definitions */
63713
63714     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
63715
63716     /* End Definitions */
63717
63718     constructor: function(config) {
63719         var me = this,
63720             surface,
63721             sprites,
63722             tipSurface;
63723         if (config.tips) {
63724             me.tipTimeout = null;
63725             me.tipConfig = Ext.apply({}, config.tips, {
63726                 renderer: Ext.emptyFn,
63727                 constrainPosition: false
63728             });
63729             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
63730             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
63731             me.chart.surface.on('mouseleave', function() {
63732                 me.hideTip();
63733             });
63734             if (me.tipConfig.surface) {
63735                 //initialize a surface
63736                 surface = me.tipConfig.surface;
63737                 sprites = surface.sprites;
63738                 tipSurface = Ext.create('Ext.chart.TipSurface', {
63739                     id: 'tipSurfaceComponent',
63740                     sprites: sprites
63741                 });
63742                 if (surface.width && surface.height) {
63743                     tipSurface.setSize(surface.width, surface.height);
63744                 }
63745                 me.tooltip.add(tipSurface);
63746                 me.spriteTip = tipSurface;
63747             }
63748         }
63749     },
63750
63751     showTip: function(item) {
63752         var me = this;
63753         if (!me.tooltip) {
63754             return;
63755         }
63756         clearTimeout(me.tipTimeout);
63757         var tooltip = me.tooltip,
63758             spriteTip = me.spriteTip,
63759             tipConfig = me.tipConfig,
63760             trackMouse = tooltip.trackMouse,
63761             sprite, surface, surfaceExt, pos, x, y;
63762         if (!trackMouse) {
63763             tooltip.trackMouse = true;
63764             sprite = item.sprite;
63765             surface = sprite.surface;
63766             surfaceExt = Ext.get(surface.getId());
63767             if (surfaceExt) {
63768                 pos = surfaceExt.getXY();
63769                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
63770                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
63771                 tooltip.targetXY = [x, y];
63772             }
63773         }
63774         if (spriteTip) {
63775             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
63776         } else {
63777             tipConfig.renderer.call(tooltip, item.storeItem, item);
63778         }
63779         tooltip.show();
63780         tooltip.trackMouse = trackMouse;
63781     },
63782
63783     hideTip: function(item) {
63784         var tooltip = this.tooltip;
63785         if (!tooltip) {
63786             return;
63787         }
63788         clearTimeout(this.tipTimeout);
63789         this.tipTimeout = setTimeout(function() {
63790             tooltip.hide();
63791         }, 0);
63792     }
63793 });
63794 /**
63795  * @class Ext.chart.axis.Abstract
63796  * Base class for all axis classes.
63797  * @private
63798  */
63799 Ext.define('Ext.chart.axis.Abstract', {
63800
63801     /* Begin Definitions */
63802
63803     requires: ['Ext.chart.Chart'],
63804
63805     /* End Definitions */
63806
63807     /**
63808      * Creates new Axis.
63809      * @param {Object} config (optional) Config options.
63810      */
63811     constructor: function(config) {
63812         config = config || {};
63813
63814         var me = this,
63815             pos = config.position || 'left';
63816
63817         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
63818         //axisLabel(Top|Bottom|Right|Left)Style
63819         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
63820         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
63821         Ext.apply(me, config);
63822         me.fields = [].concat(me.fields);
63823         this.callParent();
63824         me.labels = [];
63825         me.getId();
63826         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
63827     },
63828
63829     alignment: null,
63830     grid: false,
63831     steps: 10,
63832     x: 0,
63833     y: 0,
63834     minValue: 0,
63835     maxValue: 0,
63836
63837     getId: function() {
63838         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
63839     },
63840
63841     /*
63842       Called to process a view i.e to make aggregation and filtering over
63843       a store creating a substore to be used to render the axis. Since many axes
63844       may do different things on the data and we want the final result of all these
63845       operations to be rendered we need to call processView on all axes before drawing
63846       them.
63847     */
63848     processView: Ext.emptyFn,
63849
63850     drawAxis: Ext.emptyFn,
63851     addDisplayAndLabels: Ext.emptyFn
63852 });
63853
63854 /**
63855  * @class Ext.chart.axis.Axis
63856  * @extends Ext.chart.axis.Abstract
63857  *
63858  * Defines axis for charts. The axis position, type, style can be configured.
63859  * The axes are defined in an axes array of configuration objects where the type,
63860  * field, grid and other configuration options can be set. To know more about how
63861  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
63862  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
63863  *
63864  *     axes: [{
63865  *         type: 'Numeric',
63866  *         grid: true,
63867  *         position: 'left',
63868  *         fields: ['data1', 'data2', 'data3'],
63869  *         title: 'Number of Hits',
63870  *         grid: {
63871  *             odd: {
63872  *                 opacity: 1,
63873  *                 fill: '#ddd',
63874  *                 stroke: '#bbb',
63875  *                 'stroke-width': 1
63876  *             }
63877  *         },
63878  *         minimum: 0
63879  *     }, {
63880  *         type: 'Category',
63881  *         position: 'bottom',
63882  *         fields: ['name'],
63883  *         title: 'Month of the Year',
63884  *         grid: true,
63885  *         label: {
63886  *             rotate: {
63887  *                 degrees: 315
63888  *             }
63889  *         }
63890  *     }]
63891  *
63892  * 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
63893  * 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.
63894  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
63895  * category axis the labels will be rotated so they can fit the space better.
63896  */
63897 Ext.define('Ext.chart.axis.Axis', {
63898
63899     /* Begin Definitions */
63900
63901     extend: 'Ext.chart.axis.Abstract',
63902
63903     alternateClassName: 'Ext.chart.Axis',
63904
63905     requires: ['Ext.draw.Draw'],
63906
63907     /* End Definitions */
63908
63909     /**
63910      * @cfg {Boolean/Object} grid
63911      * The grid configuration enables you to set a background grid for an axis.
63912      * If set to *true* on a vertical axis, vertical lines will be drawn.
63913      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
63914      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
63915      *
63916      * You can set specific options for the grid configuration for odd and/or even lines/rows.
63917      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
63918      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
63919      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
63920      *
63921      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
63922      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
63923      *
63924      * For example:
63925      *
63926      *     axes: [{
63927      *         type: 'Numeric',
63928      *         grid: true,
63929      *         position: 'left',
63930      *         fields: ['data1', 'data2', 'data3'],
63931      *         title: 'Number of Hits',
63932      *         grid: {
63933      *             odd: {
63934      *                 opacity: 1,
63935      *                 fill: '#ddd',
63936      *                 stroke: '#bbb',
63937      *                 'stroke-width': 1
63938      *             }
63939      *         }
63940      *     }, {
63941      *         type: 'Category',
63942      *         position: 'bottom',
63943      *         fields: ['name'],
63944      *         title: 'Month of the Year',
63945      *         grid: true
63946      *     }]
63947      *
63948      */
63949
63950     /**
63951      * @cfg {Number} majorTickSteps
63952      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
63953      */
63954
63955     /**
63956      * @cfg {Number} minorTickSteps
63957      * The number of small ticks between two major ticks. Default is zero.
63958      */
63959
63960     /**
63961      * @cfg {String} title
63962      * The title for the Axis
63963      */
63964
63965     //@private force min/max values from store
63966     forceMinMax: false,
63967
63968     /**
63969      * @cfg {Number} dashSize
63970      * The size of the dash marker. Default's 3.
63971      */
63972     dashSize: 3,
63973
63974     /**
63975      * @cfg {String} position
63976      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
63977      */
63978     position: 'bottom',
63979
63980     // @private
63981     skipFirst: false,
63982
63983     /**
63984      * @cfg {Number} length
63985      * Offset axis position. Default's 0.
63986      */
63987     length: 0,
63988
63989     /**
63990      * @cfg {Number} width
63991      * Offset axis width. Default's 0.
63992      */
63993     width: 0,
63994
63995     majorTickSteps: false,
63996
63997     // @private
63998     applyData: Ext.emptyFn,
63999
64000     getRange: function () {
64001         var me = this,
64002             store = me.chart.getChartStore(),
64003             fields = me.fields,
64004             ln = fields.length,
64005             math = Math,
64006             mmax = math.max,
64007             mmin = math.min,
64008             aggregate = false,
64009             min = isNaN(me.minimum) ? Infinity : me.minimum,
64010             max = isNaN(me.maximum) ? -Infinity : me.maximum,
64011             total = 0, i, l, value, values, rec,
64012             excludes = [],
64013             series = me.chart.series.items;
64014
64015         //if one series is stacked I have to aggregate the values
64016         //for the scale.
64017         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
64018         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
64019         // Assuming only stack on y-axis.
64020         for (i = 0, l = series.length; !aggregate && i < l; i++) {
64021             aggregate = aggregate || (me.position == 'left' || me.position == 'right') && series[i].stacked;
64022             excludes = series[i].__excludes || excludes;
64023         }
64024         store.each(function(record) {
64025             if (aggregate) {
64026                 if (!isFinite(min)) {
64027                     min = 0;
64028                 }
64029                 for (values = [0, 0], i = 0; i < ln; i++) {
64030                     if (excludes[i]) {
64031                         continue;
64032                     }
64033                     rec = record.get(fields[i]);
64034                     values[+(rec > 0)] += math.abs(rec);
64035                 }
64036                 max = mmax(max, -values[0], +values[1]);
64037                 min = mmin(min, -values[0], +values[1]);
64038             }
64039             else {
64040                 for (i = 0; i < ln; i++) {
64041                     if (excludes[i]) {
64042                         continue;
64043                     }
64044                     value = record.get(fields[i]);
64045                     max = mmax(max, +value);
64046                     min = mmin(min, +value);
64047                 }
64048             }
64049         });
64050         if (!isFinite(max)) {
64051             max = me.prevMax || 0;
64052         }
64053         if (!isFinite(min)) {
64054             min = me.prevMin || 0;
64055         }
64056         //normalize min max for snapEnds.
64057         if (min != max && (max != Math.floor(max))) {
64058             max = Math.floor(max) + 1;
64059         }
64060
64061         if (!isNaN(me.minimum)) {
64062             min = me.minimum;
64063         }
64064         
64065         if (!isNaN(me.maximum)) {
64066             max = me.maximum;
64067         }
64068
64069         return {min: min, max: max};
64070     },
64071
64072     // @private creates a structure with start, end and step points.
64073     calcEnds: function() {
64074         var me = this,
64075             fields = me.fields,
64076             range = me.getRange(),
64077             min = range.min,
64078             max = range.max,
64079             outfrom, outto, out;
64080
64081         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
64082         outfrom = out.from;
64083         outto = out.to;
64084         if (me.forceMinMax) {
64085             if (!isNaN(max)) {
64086                 out.to = max;
64087             }
64088             if (!isNaN(min)) {
64089                 out.from = min;
64090             }
64091         }
64092         if (!isNaN(me.maximum)) {
64093             //TODO(nico) users are responsible for their own minimum/maximum values set.
64094             //Clipping should be added to remove lines in the chart which are below the axis.
64095             out.to = me.maximum;
64096         }
64097         if (!isNaN(me.minimum)) {
64098             //TODO(nico) users are responsible for their own minimum/maximum values set.
64099             //Clipping should be added to remove lines in the chart which are below the axis.
64100             out.from = me.minimum;
64101         }
64102
64103         //Adjust after adjusting minimum and maximum
64104         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
64105
64106         if (me.adjustMaximumByMajorUnit) {
64107             out.to += out.step;
64108         }
64109         if (me.adjustMinimumByMajorUnit) {
64110             out.from -= out.step;
64111         }
64112         me.prevMin = min == max? 0 : min;
64113         me.prevMax = max;
64114         return out;
64115     },
64116
64117     /**
64118      * Renders the axis into the screen and updates its position.
64119      */
64120     drawAxis: function (init) {
64121         var me = this,
64122             i, j,
64123             x = me.x,
64124             y = me.y,
64125             gutterX = me.chart.maxGutter[0],
64126             gutterY = me.chart.maxGutter[1],
64127             dashSize = me.dashSize,
64128             subDashesX = me.minorTickSteps || 0,
64129             subDashesY = me.minorTickSteps || 0,
64130             length = me.length,
64131             position = me.position,
64132             inflections = [],
64133             calcLabels = false,
64134             stepCalcs = me.applyData(),
64135             step = stepCalcs.step,
64136             steps = stepCalcs.steps,
64137             from = stepCalcs.from,
64138             to = stepCalcs.to,
64139             trueLength,
64140             currentX,
64141             currentY,
64142             path,
64143             prev,
64144             dashesX,
64145             dashesY,
64146             delta;
64147
64148         //If no steps are specified
64149         //then don't draw the axis. This generally happens
64150         //when an empty store.
64151         if (me.hidden || isNaN(step) || (from == to)) {
64152             return;
64153         }
64154
64155         me.from = stepCalcs.from;
64156         me.to = stepCalcs.to;
64157         if (position == 'left' || position == 'right') {
64158             currentX = Math.floor(x) + 0.5;
64159             path = ["M", currentX, y, "l", 0, -length];
64160             trueLength = length - (gutterY * 2);
64161         }
64162         else {
64163             currentY = Math.floor(y) + 0.5;
64164             path = ["M", x, currentY, "l", length, 0];
64165             trueLength = length - (gutterX * 2);
64166         }
64167
64168         delta = trueLength / (steps || 1);
64169         dashesX = Math.max(subDashesX +1, 0);
64170         dashesY = Math.max(subDashesY +1, 0);
64171         if (me.type == 'Numeric' || me.type == 'Time') {
64172             calcLabels = true;
64173             me.labels = [stepCalcs.from];
64174         }
64175         if (position == 'right' || position == 'left') {
64176             currentY = y - gutterY;
64177             currentX = x - ((position == 'left') * dashSize * 2);
64178             while (currentY >= y - gutterY - trueLength) {
64179                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
64180                 if (currentY != y - gutterY) {
64181                     for (i = 1; i < dashesY; i++) {
64182                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64183                     }
64184                 }
64185                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64186                 currentY -= delta;
64187                 if (calcLabels) {
64188                     me.labels.push(me.labels[me.labels.length -1] + step);
64189                 }
64190                 if (delta === 0) {
64191                     break;
64192                 }
64193             }
64194             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
64195                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
64196                 for (i = 1; i < dashesY; i++) {
64197                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64198                 }
64199                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64200                 if (calcLabels) {
64201                     me.labels.push(me.labels[me.labels.length -1] + step);
64202                 }
64203             }
64204         } else {
64205             currentX = x + gutterX;
64206             currentY = y - ((position == 'top') * dashSize * 2);
64207             while (currentX <= x + gutterX + trueLength) {
64208                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64209                 if (currentX != x + gutterX) {
64210                     for (i = 1; i < dashesX; i++) {
64211                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64212                     }
64213                 }
64214                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64215                 currentX += delta;
64216                 if (calcLabels) {
64217                     me.labels.push(me.labels[me.labels.length -1] + step);
64218                 }
64219                 if (delta === 0) {
64220                     break;
64221                 }
64222             }
64223             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
64224                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64225                 for (i = 1; i < dashesX; i++) {
64226                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64227                 }
64228                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64229                 if (calcLabels) {
64230                     me.labels.push(me.labels[me.labels.length -1] + step);
64231                 }
64232             }
64233         }
64234         if (!me.axis) {
64235             me.axis = me.chart.surface.add(Ext.apply({
64236                 type: 'path',
64237                 path: path
64238             }, me.axisStyle));
64239         }
64240         me.axis.setAttributes({
64241             path: path
64242         }, true);
64243         me.inflections = inflections;
64244         if (!init && me.grid) {
64245             me.drawGrid();
64246         }
64247         me.axisBBox = me.axis.getBBox();
64248         me.drawLabel();
64249     },
64250
64251     /**
64252      * Renders an horizontal and/or vertical grid into the Surface.
64253      */
64254     drawGrid: function() {
64255         var me = this,
64256             surface = me.chart.surface,
64257             grid = me.grid,
64258             odd = grid.odd,
64259             even = grid.even,
64260             inflections = me.inflections,
64261             ln = inflections.length - ((odd || even)? 0 : 1),
64262             position = me.position,
64263             gutter = me.chart.maxGutter,
64264             width = me.width - 2,
64265             vert = false,
64266             point, prevPoint,
64267             i = 1,
64268             path = [], styles, lineWidth, dlineWidth,
64269             oddPath = [], evenPath = [];
64270
64271         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
64272             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
64273             i = 0;
64274             ln++;
64275         }
64276         for (; i < ln; i++) {
64277             point = inflections[i];
64278             prevPoint = inflections[i - 1];
64279             if (odd || even) {
64280                 path = (i % 2)? oddPath : evenPath;
64281                 styles = ((i % 2)? odd : even) || {};
64282                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
64283                 dlineWidth = 2 * lineWidth;
64284                 if (position == 'left') {
64285                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64286                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64287                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
64288                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
64289                 }
64290                 else if (position == 'right') {
64291                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64292                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64293                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
64294                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
64295                 }
64296                 else if (position == 'top') {
64297                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
64298                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
64299                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
64300                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
64301                 }
64302                 else {
64303                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
64304                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
64305                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
64306                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
64307                 }
64308             } else {
64309                 if (position == 'left') {
64310                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
64311                 }
64312                 else if (position == 'right') {
64313                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
64314                 }
64315                 else if (position == 'top') {
64316                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
64317                 }
64318                 else {
64319                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
64320                 }
64321             }
64322         }
64323         if (odd || even) {
64324             if (oddPath.length) {
64325                 if (!me.gridOdd && oddPath.length) {
64326                     me.gridOdd = surface.add({
64327                         type: 'path',
64328                         path: oddPath
64329                     });
64330                 }
64331                 me.gridOdd.setAttributes(Ext.apply({
64332                     path: oddPath,
64333                     hidden: false
64334                 }, odd || {}), true);
64335             }
64336             if (evenPath.length) {
64337                 if (!me.gridEven) {
64338                     me.gridEven = surface.add({
64339                         type: 'path',
64340                         path: evenPath
64341                     });
64342                 }
64343                 me.gridEven.setAttributes(Ext.apply({
64344                     path: evenPath,
64345                     hidden: false
64346                 }, even || {}), true);
64347             }
64348         }
64349         else {
64350             if (path.length) {
64351                 if (!me.gridLines) {
64352                     me.gridLines = me.chart.surface.add({
64353                         type: 'path',
64354                         path: path,
64355                         "stroke-width": me.lineWidth || 1,
64356                         stroke: me.gridColor || '#ccc'
64357                     });
64358                 }
64359                 me.gridLines.setAttributes({
64360                     hidden: false,
64361                     path: path
64362                 }, true);
64363             }
64364             else if (me.gridLines) {
64365                 me.gridLines.hide(true);
64366             }
64367         }
64368     },
64369
64370     //@private
64371     getOrCreateLabel: function(i, text) {
64372         var me = this,
64373             labelGroup = me.labelGroup,
64374             textLabel = labelGroup.getAt(i),
64375             surface = me.chart.surface;
64376         if (textLabel) {
64377             if (text != textLabel.attr.text) {
64378                 textLabel.setAttributes(Ext.apply({
64379                     text: text
64380                 }, me.label), true);
64381                 textLabel._bbox = textLabel.getBBox();
64382             }
64383         }
64384         else {
64385             textLabel = surface.add(Ext.apply({
64386                 group: labelGroup,
64387                 type: 'text',
64388                 x: 0,
64389                 y: 0,
64390                 text: text
64391             }, me.label));
64392             surface.renderItem(textLabel);
64393             textLabel._bbox = textLabel.getBBox();
64394         }
64395         //get untransformed bounding box
64396         if (me.label.rotation) {
64397             textLabel.setAttributes({
64398                 rotation: {
64399                     degrees: 0
64400                 }
64401             }, true);
64402             textLabel._ubbox = textLabel.getBBox();
64403             textLabel.setAttributes(me.label, true);
64404         } else {
64405             textLabel._ubbox = textLabel._bbox;
64406         }
64407         return textLabel;
64408     },
64409
64410     rect2pointArray: function(sprite) {
64411         var surface = this.chart.surface,
64412             rect = surface.getBBox(sprite, true),
64413             p1 = [rect.x, rect.y],
64414             p1p = p1.slice(),
64415             p2 = [rect.x + rect.width, rect.y],
64416             p2p = p2.slice(),
64417             p3 = [rect.x + rect.width, rect.y + rect.height],
64418             p3p = p3.slice(),
64419             p4 = [rect.x, rect.y + rect.height],
64420             p4p = p4.slice(),
64421             matrix = sprite.matrix;
64422         //transform the points
64423         p1[0] = matrix.x.apply(matrix, p1p);
64424         p1[1] = matrix.y.apply(matrix, p1p);
64425
64426         p2[0] = matrix.x.apply(matrix, p2p);
64427         p2[1] = matrix.y.apply(matrix, p2p);
64428
64429         p3[0] = matrix.x.apply(matrix, p3p);
64430         p3[1] = matrix.y.apply(matrix, p3p);
64431
64432         p4[0] = matrix.x.apply(matrix, p4p);
64433         p4[1] = matrix.y.apply(matrix, p4p);
64434         return [p1, p2, p3, p4];
64435     },
64436
64437     intersect: function(l1, l2) {
64438         var r1 = this.rect2pointArray(l1),
64439             r2 = this.rect2pointArray(l2);
64440         return !!Ext.draw.Draw.intersect(r1, r2).length;
64441     },
64442
64443     drawHorizontalLabels: function() {
64444        var  me = this,
64445             labelConf = me.label,
64446             floor = Math.floor,
64447             max = Math.max,
64448             axes = me.chart.axes,
64449             position = me.position,
64450             inflections = me.inflections,
64451             ln = inflections.length,
64452             labels = me.labels,
64453             labelGroup = me.labelGroup,
64454             maxHeight = 0,
64455             ratio,
64456             gutterY = me.chart.maxGutter[1],
64457             ubbox, bbox, point, prevX, prevLabel,
64458             projectedWidth = 0,
64459             textLabel, attr, textRight, text,
64460             label, last, x, y, i, firstLabel;
64461
64462         last = ln - 1;
64463         //get a reference to the first text label dimensions
64464         point = inflections[0];
64465         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
64466         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
64467
64468         for (i = 0; i < ln; i++) {
64469             point = inflections[i];
64470             text = me.label.renderer(labels[i]);
64471             textLabel = me.getOrCreateLabel(i, text);
64472             bbox = textLabel._bbox;
64473             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
64474             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
64475             if (me.chart.maxGutter[0] == 0) {
64476                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
64477                     x = point[0];
64478                 }
64479                 else if (i == last && axes.findIndex('position', 'right') == -1) {
64480                     x = point[0] - bbox.width;
64481                 }
64482             }
64483             if (position == 'top') {
64484                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
64485             }
64486             else {
64487                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
64488             }
64489
64490             textLabel.setAttributes({
64491                 hidden: false,
64492                 x: x,
64493                 y: y
64494             }, true);
64495
64496             // Skip label if there isn't available minimum space
64497             if (i != 0 && (me.intersect(textLabel, prevLabel)
64498                 || me.intersect(textLabel, firstLabel))) {
64499                 textLabel.hide(true);
64500                 continue;
64501             }
64502
64503             prevLabel = textLabel;
64504         }
64505
64506         return maxHeight;
64507     },
64508
64509     drawVerticalLabels: function() {
64510         var me = this,
64511             inflections = me.inflections,
64512             position = me.position,
64513             ln = inflections.length,
64514             labels = me.labels,
64515             maxWidth = 0,
64516             max = Math.max,
64517             floor = Math.floor,
64518             ceil = Math.ceil,
64519             axes = me.chart.axes,
64520             gutterY = me.chart.maxGutter[1],
64521             ubbox, bbox, point, prevLabel,
64522             projectedWidth = 0,
64523             textLabel, attr, textRight, text,
64524             label, last, x, y, i;
64525
64526         last = ln;
64527         for (i = 0; i < last; i++) {
64528             point = inflections[i];
64529             text = me.label.renderer(labels[i]);
64530             textLabel = me.getOrCreateLabel(i, text);
64531             bbox = textLabel._bbox;
64532
64533             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
64534             y = point[1];
64535             if (gutterY < bbox.height / 2) {
64536                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
64537                     y = me.y - me.length + ceil(bbox.height / 2);
64538                 }
64539                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
64540                     y = me.y - floor(bbox.height / 2);
64541                 }
64542             }
64543             if (position == 'left') {
64544                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
64545             }
64546             else {
64547                 x = point[0] + me.dashSize + me.label.padding + 2;
64548             }
64549             textLabel.setAttributes(Ext.apply({
64550                 hidden: false,
64551                 x: x,
64552                 y: y
64553             }, me.label), true);
64554             // Skip label if there isn't available minimum space
64555             if (i != 0 && me.intersect(textLabel, prevLabel)) {
64556                 textLabel.hide(true);
64557                 continue;
64558             }
64559             prevLabel = textLabel;
64560         }
64561
64562         return maxWidth;
64563     },
64564
64565     /**
64566      * Renders the labels in the axes.
64567      */
64568     drawLabel: function() {
64569         var me = this,
64570             position = me.position,
64571             labelGroup = me.labelGroup,
64572             inflections = me.inflections,
64573             maxWidth = 0,
64574             maxHeight = 0,
64575             ln, i;
64576
64577         if (position == 'left' || position == 'right') {
64578             maxWidth = me.drawVerticalLabels();
64579         } else {
64580             maxHeight = me.drawHorizontalLabels();
64581         }
64582
64583         // Hide unused bars
64584         ln = labelGroup.getCount();
64585         i = inflections.length;
64586         for (; i < ln; i++) {
64587             labelGroup.getAt(i).hide(true);
64588         }
64589
64590         me.bbox = {};
64591         Ext.apply(me.bbox, me.axisBBox);
64592         me.bbox.height = maxHeight;
64593         me.bbox.width = maxWidth;
64594         if (Ext.isString(me.title)) {
64595             me.drawTitle(maxWidth, maxHeight);
64596         }
64597     },
64598
64599     // @private creates the elipsis for the text.
64600     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
64601         var bbox,
64602             x;
64603
64604         if (desiredWidth < minWidth) {
64605             sprite.hide(true);
64606             return false;
64607         }
64608         while (text.length > 4) {
64609             text = text.substr(0, text.length - 4) + "...";
64610             sprite.setAttributes({
64611                 text: text
64612             }, true);
64613             bbox = sprite.getBBox();
64614             if (bbox.width < desiredWidth) {
64615                 if (typeof center == 'number') {
64616                     sprite.setAttributes({
64617                         x: Math.floor(center - (bbox.width / 2))
64618                     }, true);
64619                 }
64620                 break;
64621             }
64622         }
64623         return true;
64624     },
64625
64626     /**
64627      * Updates the {@link #title} of this axis.
64628      * @param {String} title
64629      */
64630     setTitle: function(title) {
64631         this.title = title;
64632         this.drawLabel();
64633     },
64634
64635     // @private draws the title for the axis.
64636     drawTitle: function(maxWidth, maxHeight) {
64637         var me = this,
64638             position = me.position,
64639             surface = me.chart.surface,
64640             displaySprite = me.displaySprite,
64641             title = me.title,
64642             rotate = (position == 'left' || position == 'right'),
64643             x = me.x,
64644             y = me.y,
64645             base, bbox, pad;
64646
64647         if (displaySprite) {
64648             displaySprite.setAttributes({text: title}, true);
64649         } else {
64650             base = {
64651                 type: 'text',
64652                 x: 0,
64653                 y: 0,
64654                 text: title
64655             };
64656             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
64657             surface.renderItem(displaySprite);
64658         }
64659         bbox = displaySprite.getBBox();
64660         pad = me.dashSize + me.label.padding;
64661
64662         if (rotate) {
64663             y -= ((me.length / 2) - (bbox.height / 2));
64664             if (position == 'left') {
64665                 x -= (maxWidth + pad + (bbox.width / 2));
64666             }
64667             else {
64668                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
64669             }
64670             me.bbox.width += bbox.width + 10;
64671         }
64672         else {
64673             x += (me.length / 2) - (bbox.width * 0.5);
64674             if (position == 'top') {
64675                 y -= (maxHeight + pad + (bbox.height * 0.3));
64676             }
64677             else {
64678                 y += (maxHeight + pad + (bbox.height * 0.8));
64679             }
64680             me.bbox.height += bbox.height + 10;
64681         }
64682         displaySprite.setAttributes({
64683             translate: {
64684                 x: x,
64685                 y: y
64686             }
64687         }, true);
64688     }
64689 });
64690
64691 /**
64692  * @class Ext.chart.axis.Category
64693  * @extends Ext.chart.axis.Axis
64694  *
64695  * A type of axis that displays items in categories. This axis is generally used to
64696  * display categorical information like names of items, month names, quarters, etc.
64697  * but no quantitative values. For that other type of information `Number`
64698  * axis are more suitable.
64699  *
64700  * As with other axis you can set the position of the axis and its title. For example:
64701  *
64702  *     @example
64703  *     var store = Ext.create('Ext.data.JsonStore', {
64704  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
64705  *         data: [
64706  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
64707  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
64708  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
64709  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
64710  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
64711  *         ]
64712  *     });
64713  *
64714  *     Ext.create('Ext.chart.Chart', {
64715  *         renderTo: Ext.getBody(),
64716  *         width: 500,
64717  *         height: 300,
64718  *         store: store,
64719  *         axes: [{
64720  *             type: 'Numeric',
64721  *             grid: true,
64722  *             position: 'left',
64723  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
64724  *             title: 'Sample Values',
64725  *             grid: {
64726  *                 odd: {
64727  *                     opacity: 1,
64728  *                     fill: '#ddd',
64729  *                     stroke: '#bbb',
64730  *                     'stroke-width': 1
64731  *                 }
64732  *             },
64733  *             minimum: 0,
64734  *             adjustMinimumByMajorUnit: 0
64735  *         }, {
64736  *             type: 'Category',
64737  *             position: 'bottom',
64738  *             fields: ['name'],
64739  *             title: 'Sample Metrics',
64740  *             grid: true,
64741  *             label: {
64742  *                 rotate: {
64743  *                     degrees: 315
64744  *                 }
64745  *             }
64746  *         }],
64747  *         series: [{
64748  *             type: 'area',
64749  *             highlight: false,
64750  *             axis: 'left',
64751  *             xField: 'name',
64752  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
64753  *             style: {
64754  *                 opacity: 0.93
64755  *             }
64756  *         }]
64757  *     });
64758  *
64759  * In this example with set the category axis to the bottom of the surface, bound the axis to
64760  * the `name` property and set as title _Month of the Year_.
64761  */
64762 Ext.define('Ext.chart.axis.Category', {
64763
64764     /* Begin Definitions */
64765
64766     extend: 'Ext.chart.axis.Axis',
64767
64768     alternateClassName: 'Ext.chart.CategoryAxis',
64769
64770     alias: 'axis.category',
64771
64772     /* End Definitions */
64773
64774     /**
64775      * A list of category names to display along this axis.
64776      * @property {String} categoryNames
64777      */
64778     categoryNames: null,
64779
64780     /**
64781      * Indicates whether or not to calculate the number of categories (ticks and
64782      * labels) when there is not enough room to display all labels on the axis.
64783      * If set to true, the axis will determine the number of categories to plot.
64784      * If not, all categories will be plotted.
64785      *
64786      * @property calculateCategoryCount
64787      * @type Boolean
64788      */
64789     calculateCategoryCount: false,
64790
64791     // @private creates an array of labels to be used when rendering.
64792     setLabels: function() {
64793         var store = this.chart.store,
64794             fields = this.fields,
64795             ln = fields.length,
64796             i;
64797
64798         this.labels = [];
64799         store.each(function(record) {
64800             for (i = 0; i < ln; i++) {
64801                 this.labels.push(record.get(fields[i]));
64802             }
64803         }, this);
64804     },
64805
64806     // @private calculates labels positions and marker positions for rendering.
64807     applyData: function() {
64808         this.callParent();
64809         this.setLabels();
64810         var count = this.chart.store.getCount();
64811         return {
64812             from: 0,
64813             to: count,
64814             power: 1,
64815             step: 1,
64816             steps: count - 1
64817         };
64818     }
64819 });
64820
64821 /**
64822  * @class Ext.chart.axis.Gauge
64823  * @extends Ext.chart.axis.Abstract
64824  *
64825  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
64826  * displays numeric data from an interval defined by the `minimum`, `maximum` and
64827  * `step` configuration properties. The placement of the numeric data can be changed
64828  * by altering the `margin` option that is set to `10` by default.
64829  *
64830  * A possible configuration for this axis would look like:
64831  *
64832  *     axes: [{
64833  *         type: 'gauge',
64834  *         position: 'gauge',
64835  *         minimum: 0,
64836  *         maximum: 100,
64837  *         steps: 10,
64838  *         margin: 7
64839  *     }],
64840  */
64841 Ext.define('Ext.chart.axis.Gauge', {
64842
64843     /* Begin Definitions */
64844
64845     extend: 'Ext.chart.axis.Abstract',
64846
64847     /* End Definitions */
64848
64849     /**
64850      * @cfg {Number} minimum (required)
64851      * The minimum value of the interval to be displayed in the axis.
64852      */
64853
64854     /**
64855      * @cfg {Number} maximum (required)
64856      * The maximum value of the interval to be displayed in the axis.
64857      */
64858
64859     /**
64860      * @cfg {Number} steps (required)
64861      * The number of steps and tick marks to add to the interval.
64862      */
64863
64864     /**
64865      * @cfg {Number} [margin=10]
64866      * The offset positioning of the tick marks and labels in pixels.
64867      */
64868
64869     /**
64870      * @cfg {String} title
64871      * The title for the Axis.
64872      */
64873
64874     position: 'gauge',
64875
64876     alias: 'axis.gauge',
64877
64878     drawAxis: function(init) {
64879         var chart = this.chart,
64880             surface = chart.surface,
64881             bbox = chart.chartBBox,
64882             centerX = bbox.x + (bbox.width / 2),
64883             centerY = bbox.y + bbox.height,
64884             margin = this.margin || 10,
64885             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
64886             sprites = [], sprite,
64887             steps = this.steps,
64888             i, pi = Math.PI,
64889             cos = Math.cos,
64890             sin = Math.sin;
64891
64892         if (this.sprites && !chart.resizing) {
64893             this.drawLabel();
64894             return;
64895         }
64896
64897         if (this.margin >= 0) {
64898             if (!this.sprites) {
64899                 //draw circles
64900                 for (i = 0; i <= steps; i++) {
64901                     sprite = surface.add({
64902                         type: 'path',
64903                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64904                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64905                                     'L', centerX + rho * cos(i / steps * pi - pi),
64906                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64907                         stroke: '#ccc'
64908                     });
64909                     sprite.setAttributes({
64910                         hidden: false
64911                     }, true);
64912                     sprites.push(sprite);
64913                 }
64914             } else {
64915                 sprites = this.sprites;
64916                 //draw circles
64917                 for (i = 0; i <= steps; i++) {
64918                     sprites[i].setAttributes({
64919                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64920                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64921                                'L', centerX + rho * cos(i / steps * pi - pi),
64922                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64923                         stroke: '#ccc'
64924                     }, true);
64925                 }
64926             }
64927         }
64928         this.sprites = sprites;
64929         this.drawLabel();
64930         if (this.title) {
64931             this.drawTitle();
64932         }
64933     },
64934
64935     drawTitle: function() {
64936         var me = this,
64937             chart = me.chart,
64938             surface = chart.surface,
64939             bbox = chart.chartBBox,
64940             labelSprite = me.titleSprite,
64941             labelBBox;
64942
64943         if (!labelSprite) {
64944             me.titleSprite = labelSprite = surface.add({
64945                 type: 'text',
64946                 zIndex: 2
64947             });
64948         }
64949         labelSprite.setAttributes(Ext.apply({
64950             text: me.title
64951         }, me.label || {}), true);
64952         labelBBox = labelSprite.getBBox();
64953         labelSprite.setAttributes({
64954             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
64955             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
64956         }, true);
64957     },
64958
64959     /**
64960      * Updates the {@link #title} of this axis.
64961      * @param {String} title
64962      */
64963     setTitle: function(title) {
64964         this.title = title;
64965         this.drawTitle();
64966     },
64967
64968     drawLabel: function() {
64969         var chart = this.chart,
64970             surface = chart.surface,
64971             bbox = chart.chartBBox,
64972             centerX = bbox.x + (bbox.width / 2),
64973             centerY = bbox.y + bbox.height,
64974             margin = this.margin || 10,
64975             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
64976             round = Math.round,
64977             labelArray = [], label,
64978             maxValue = this.maximum || 0,
64979             steps = this.steps, i = 0,
64980             adjY,
64981             pi = Math.PI,
64982             cos = Math.cos,
64983             sin = Math.sin,
64984             labelConf = this.label,
64985             renderer = labelConf.renderer || function(v) { return v; };
64986
64987         if (!this.labelArray) {
64988             //draw scale
64989             for (i = 0; i <= steps; i++) {
64990                 // TODO Adjust for height of text / 2 instead
64991                 adjY = (i === 0 || i === steps) ? 7 : 0;
64992                 label = surface.add({
64993                     type: 'text',
64994                     text: renderer(round(i / steps * maxValue)),
64995                     x: centerX + rho * cos(i / steps * pi - pi),
64996                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
64997                     'text-anchor': 'middle',
64998                     'stroke-width': 0.2,
64999                     zIndex: 10,
65000                     stroke: '#333'
65001                 });
65002                 label.setAttributes({
65003                     hidden: false
65004                 }, true);
65005                 labelArray.push(label);
65006             }
65007         }
65008         else {
65009             labelArray = this.labelArray;
65010             //draw values
65011             for (i = 0; i <= steps; i++) {
65012                 // TODO Adjust for height of text / 2 instead
65013                 adjY = (i === 0 || i === steps) ? 7 : 0;
65014                 labelArray[i].setAttributes({
65015                     text: renderer(round(i / steps * maxValue)),
65016                     x: centerX + rho * cos(i / steps * pi - pi),
65017                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
65018                 }, true);
65019             }
65020         }
65021         this.labelArray = labelArray;
65022     }
65023 });
65024 /**
65025  * @class Ext.chart.axis.Numeric
65026  * @extends Ext.chart.axis.Axis
65027  *
65028  * An axis to handle numeric values. This axis is used for quantitative data as
65029  * opposed to the category axis. You can set mininum and maximum values to the
65030  * axis so that the values are bound to that. If no values are set, then the
65031  * scale will auto-adjust to the values.
65032  *
65033  *     @example
65034  *     var store = Ext.create('Ext.data.JsonStore', {
65035  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65036  *          data: [
65037  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65038  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65039  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65040  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65041  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65042  *          ]
65043  *     });
65044  *
65045  *     Ext.create('Ext.chart.Chart', {
65046  *         renderTo: Ext.getBody(),
65047  *         width: 500,
65048  *         height: 300,
65049  *         store: store,
65050  *         axes: [{
65051  *             type: 'Numeric',
65052  *             grid: true,
65053  *             position: 'left',
65054  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65055  *             title: 'Sample Values',
65056  *             grid: {
65057  *                 odd: {
65058  *                     opacity: 1,
65059  *                     fill: '#ddd',
65060  *                     stroke: '#bbb',
65061  *                     'stroke-width': 1
65062  *                 }
65063  *             },
65064  *             minimum: 0,
65065  *             adjustMinimumByMajorUnit: 0
65066  *         }, {
65067  *             type: 'Category',
65068  *             position: 'bottom',
65069  *             fields: ['name'],
65070  *             title: 'Sample Metrics',
65071  *             grid: true,
65072  *             label: {
65073  *                 rotate: {
65074  *                     degrees: 315
65075  *                 }
65076  *             }
65077  *         }],
65078  *         series: [{
65079  *             type: 'area',
65080  *             highlight: false,
65081  *             axis: 'left',
65082  *             xField: 'name',
65083  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65084  *             style: {
65085  *                 opacity: 0.93
65086  *             }
65087  *         }]
65088  *     });
65089  *
65090  * In this example we create an axis of Numeric type. We set a minimum value so that
65091  * even if all series have values greater than zero, the grid starts at zero. We bind
65092  * the axis onto the left part of the surface by setting `position` to `left`.
65093  * We bind three different store fields to this axis by setting `fields` to an array.
65094  * We set the title of the axis to _Number of Hits_ by using the `title` property.
65095  * We use a `grid` configuration to set odd background rows to a certain style and even rows
65096  * to be transparent/ignored.
65097  */
65098 Ext.define('Ext.chart.axis.Numeric', {
65099
65100     /* Begin Definitions */
65101
65102     extend: 'Ext.chart.axis.Axis',
65103
65104     alternateClassName: 'Ext.chart.NumericAxis',
65105
65106     /* End Definitions */
65107
65108     type: 'numeric',
65109
65110     alias: 'axis.numeric',
65111
65112     constructor: function(config) {
65113         var me = this,
65114             hasLabel = !!(config.label && config.label.renderer),
65115             label;
65116
65117         me.callParent([config]);
65118         label = me.label;
65119         if (me.roundToDecimal === false) {
65120             return;
65121         }
65122         if (!hasLabel) {
65123             label.renderer = function(v) {
65124                 return me.roundToDecimal(v, me.decimals);
65125             };
65126         }
65127     },
65128
65129     roundToDecimal: function(v, dec) {
65130         var val = Math.pow(10, dec || 0);
65131         return Math.floor(v * val) / val;
65132     },
65133
65134     /**
65135      * The minimum value drawn by the axis. If not set explicitly, the axis
65136      * minimum will be calculated automatically.
65137      *
65138      * @property {Number} minimum
65139      */
65140     minimum: NaN,
65141
65142     /**
65143      * The maximum value drawn by the axis. If not set explicitly, the axis
65144      * maximum will be calculated automatically.
65145      *
65146      * @property {Number} maximum
65147      */
65148     maximum: NaN,
65149
65150     /**
65151      * The number of decimals to round the value to.
65152      *
65153      * @property {Number} decimals
65154      */
65155     decimals: 2,
65156
65157     /**
65158      * The scaling algorithm to use on this axis. May be "linear" or
65159      * "logarithmic".  Currently only linear scale is implemented.
65160      *
65161      * @property {String} scale
65162      * @private
65163      */
65164     scale: "linear",
65165
65166     /**
65167      * Indicates the position of the axis relative to the chart
65168      *
65169      * @property {String} position
65170      */
65171     position: 'left',
65172
65173     /**
65174      * Indicates whether to extend maximum beyond data's maximum to the nearest
65175      * majorUnit.
65176      *
65177      * @property {Boolean} adjustMaximumByMajorUnit
65178      */
65179     adjustMaximumByMajorUnit: false,
65180
65181     /**
65182      * Indicates whether to extend the minimum beyond data's minimum to the
65183      * nearest majorUnit.
65184      *
65185      * @property {Boolean} adjustMinimumByMajorUnit
65186      */
65187     adjustMinimumByMajorUnit: false,
65188
65189     // @private apply data.
65190     applyData: function() {
65191         this.callParent();
65192         return this.calcEnds();
65193     }
65194 });
65195
65196 /**
65197  * @class Ext.chart.axis.Radial
65198  * @extends Ext.chart.axis.Abstract
65199  * @ignore
65200  */
65201 Ext.define('Ext.chart.axis.Radial', {
65202
65203     /* Begin Definitions */
65204
65205     extend: 'Ext.chart.axis.Abstract',
65206
65207     /* End Definitions */
65208
65209     position: 'radial',
65210
65211     alias: 'axis.radial',
65212
65213     drawAxis: function(init) {
65214         var chart = this.chart,
65215             surface = chart.surface,
65216             bbox = chart.chartBBox,
65217             store = chart.store,
65218             l = store.getCount(),
65219             centerX = bbox.x + (bbox.width / 2),
65220             centerY = bbox.y + (bbox.height / 2),
65221             rho = Math.min(bbox.width, bbox.height) /2,
65222             sprites = [], sprite,
65223             steps = this.steps,
65224             i, j, pi2 = Math.PI * 2,
65225             cos = Math.cos, sin = Math.sin;
65226
65227         if (this.sprites && !chart.resizing) {
65228             this.drawLabel();
65229             return;
65230         }
65231
65232         if (!this.sprites) {
65233             //draw circles
65234             for (i = 1; i <= steps; i++) {
65235                 sprite = surface.add({
65236                     type: 'circle',
65237                     x: centerX,
65238                     y: centerY,
65239                     radius: Math.max(rho * i / steps, 0),
65240                     stroke: '#ccc'
65241                 });
65242                 sprite.setAttributes({
65243                     hidden: false
65244                 }, true);
65245                 sprites.push(sprite);
65246             }
65247             //draw lines
65248             store.each(function(rec, i) {
65249                 sprite = surface.add({
65250                     type: 'path',
65251                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
65252                     stroke: '#ccc'
65253                 });
65254                 sprite.setAttributes({
65255                     hidden: false
65256                 }, true);
65257                 sprites.push(sprite);
65258             });
65259         } else {
65260             sprites = this.sprites;
65261             //draw circles
65262             for (i = 0; i < steps; i++) {
65263                 sprites[i].setAttributes({
65264                     x: centerX,
65265                     y: centerY,
65266                     radius: Math.max(rho * (i + 1) / steps, 0),
65267                     stroke: '#ccc'
65268                 }, true);
65269             }
65270             //draw lines
65271             store.each(function(rec, j) {
65272                 sprites[i + j].setAttributes({
65273                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
65274                     stroke: '#ccc'
65275                 }, true);
65276             });
65277         }
65278         this.sprites = sprites;
65279
65280         this.drawLabel();
65281     },
65282
65283     drawLabel: function() {
65284         var chart = this.chart,
65285             surface = chart.surface,
65286             bbox = chart.chartBBox,
65287             store = chart.store,
65288             centerX = bbox.x + (bbox.width / 2),
65289             centerY = bbox.y + (bbox.height / 2),
65290             rho = Math.min(bbox.width, bbox.height) /2,
65291             max = Math.max, round = Math.round,
65292             labelArray = [], label,
65293             fields = [], nfields,
65294             categories = [], xField,
65295             aggregate = !this.maximum,
65296             maxValue = this.maximum || 0,
65297             steps = this.steps, i = 0, j, dx, dy,
65298             pi2 = Math.PI * 2,
65299             cos = Math.cos, sin = Math.sin,
65300             display = this.label.display,
65301             draw = display !== 'none',
65302             margin = 10;
65303
65304         if (!draw) {
65305             return;
65306         }
65307
65308         //get all rendered fields
65309         chart.series.each(function(series) {
65310             fields.push(series.yField);
65311             xField = series.xField;
65312         });
65313         
65314         //get maxValue to interpolate
65315         store.each(function(record, i) {
65316             if (aggregate) {
65317                 for (i = 0, nfields = fields.length; i < nfields; i++) {
65318                     maxValue = max(+record.get(fields[i]), maxValue);
65319                 }
65320             }
65321             categories.push(record.get(xField));
65322         });
65323         if (!this.labelArray) {
65324             if (display != 'categories') {
65325                 //draw scale
65326                 for (i = 1; i <= steps; i++) {
65327                     label = surface.add({
65328                         type: 'text',
65329                         text: round(i / steps * maxValue),
65330                         x: centerX,
65331                         y: centerY - rho * i / steps,
65332                         'text-anchor': 'middle',
65333                         'stroke-width': 0.1,
65334                         stroke: '#333'
65335                     });
65336                     label.setAttributes({
65337                         hidden: false
65338                     }, true);
65339                     labelArray.push(label);
65340                 }
65341             }
65342             if (display != 'scale') {
65343                 //draw text
65344                 for (j = 0, steps = categories.length; j < steps; j++) {
65345                     dx = cos(j / steps * pi2) * (rho + margin);
65346                     dy = sin(j / steps * pi2) * (rho + margin);
65347                     label = surface.add({
65348                         type: 'text',
65349                         text: categories[j],
65350                         x: centerX + dx,
65351                         y: centerY + dy,
65352                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65353                     });
65354                     label.setAttributes({
65355                         hidden: false
65356                     }, true);
65357                     labelArray.push(label);
65358                 }
65359             }
65360         }
65361         else {
65362             labelArray = this.labelArray;
65363             if (display != 'categories') {
65364                 //draw values
65365                 for (i = 0; i < steps; i++) {
65366                     labelArray[i].setAttributes({
65367                         text: round((i + 1) / steps * maxValue),
65368                         x: centerX,
65369                         y: centerY - rho * (i + 1) / steps,
65370                         'text-anchor': 'middle',
65371                         'stroke-width': 0.1,
65372                         stroke: '#333'
65373                     }, true);
65374                 }
65375             }
65376             if (display != 'scale') {
65377                 //draw text
65378                 for (j = 0, steps = categories.length; j < steps; j++) {
65379                     dx = cos(j / steps * pi2) * (rho + margin);
65380                     dy = sin(j / steps * pi2) * (rho + margin);
65381                     if (labelArray[i + j]) {
65382                         labelArray[i + j].setAttributes({
65383                             type: 'text',
65384                             text: categories[j],
65385                             x: centerX + dx,
65386                             y: centerY + dy,
65387                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65388                         }, true);
65389                     }
65390                 }
65391             }
65392         }
65393         this.labelArray = labelArray;
65394     }
65395 });
65396 /**
65397  * @author Ed Spencer
65398  *
65399  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
65400  * but offers a set of methods used by both of those subclasses.
65401  * 
65402  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
65403  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
65404  * AbstractStore is and is not.
65405  * 
65406  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
65407  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
65408  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
65409  * 
65410  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
65411  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
65412  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
65413  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
65414  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
65415  * 
65416  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
65417  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
65418  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
65419  * {@link Ext.data.Store#filter filter} methods.
65420  */
65421 Ext.define('Ext.data.AbstractStore', {
65422     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
65423     
65424     mixins: {
65425         observable: 'Ext.util.Observable',
65426         sortable: 'Ext.util.Sortable'
65427     },
65428     
65429     statics: {
65430         create: function(store){
65431             if (!store.isStore) {
65432                 if (!store.type) {
65433                     store.type = 'store';
65434                 }
65435                 store = Ext.createByAlias('store.' + store.type, store);
65436             }
65437             return store;
65438         }    
65439     },
65440     
65441     remoteSort  : false,
65442     remoteFilter: false,
65443
65444     /**
65445      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
65446      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
65447      * see {@link #setProxy} for details.
65448      */
65449
65450     /**
65451      * @cfg {Boolean/Object} autoLoad
65452      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
65453      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
65454      * Defaults to false.
65455      */
65456     autoLoad: false,
65457
65458     /**
65459      * @cfg {Boolean} autoSync
65460      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
65461      */
65462     autoSync: false,
65463
65464     /**
65465      * @property {String} batchUpdateMode
65466      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
65467      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
65468      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
65469      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
65470      */
65471     batchUpdateMode: 'operation',
65472
65473     /**
65474      * @property {Boolean} filterOnLoad
65475      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
65476      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
65477      */
65478     filterOnLoad: true,
65479
65480     /**
65481      * @property {Boolean} sortOnLoad
65482      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
65483      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
65484      */
65485     sortOnLoad: true,
65486
65487     /**
65488      * @property {Boolean} implicitModel
65489      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
65490      * constructor instead of a model constructor or name.
65491      * @private
65492      */
65493     implicitModel: false,
65494
65495     /**
65496      * @property {String} defaultProxyType
65497      * The string type of the Proxy to create if none is specified. This defaults to creating a
65498      * {@link Ext.data.proxy.Memory memory proxy}.
65499      */
65500     defaultProxyType: 'memory',
65501
65502     /**
65503      * @property {Boolean} isDestroyed
65504      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
65505      * as it will not function correctly any more.
65506      */
65507     isDestroyed: false,
65508
65509     isStore: true,
65510
65511     /**
65512      * @cfg {String} storeId
65513      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
65514      * making it easy to reuse elsewhere. Defaults to undefined.
65515      */
65516     
65517     /**
65518      * @cfg {Object[]} fields
65519      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
65520      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
65521      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
65522      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
65523      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
65524      * config.
65525      */
65526
65527     /**
65528      * @cfg {String} model
65529      * Name of the {@link Ext.data.Model Model} associated with this store.
65530      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
65531      */
65532
65533     sortRoot: 'data',
65534     
65535     //documented above
65536     constructor: function(config) {
65537         var me = this,
65538             filters;
65539         
65540         me.addEvents(
65541             /**
65542              * @event add
65543              * Fired when a Model instance has been added to this Store
65544              * @param {Ext.data.Store} store The store
65545              * @param {Ext.data.Model[]} records The Model instances that were added
65546              * @param {Number} index The index at which the instances were inserted
65547              */
65548             'add',
65549
65550             /**
65551              * @event remove
65552              * Fired when a Model instance has been removed from this Store
65553              * @param {Ext.data.Store} store The Store object
65554              * @param {Ext.data.Model} record The record that was removed
65555              * @param {Number} index The index of the record that was removed
65556              */
65557             'remove',
65558             
65559             /**
65560              * @event update
65561              * Fires when a Model instance has been updated
65562              * @param {Ext.data.Store} this
65563              * @param {Ext.data.Model} record The Model instance that was updated
65564              * @param {String} operation The update operation being performed. Value may be one of:
65565              *
65566              *     Ext.data.Model.EDIT
65567              *     Ext.data.Model.REJECT
65568              *     Ext.data.Model.COMMIT
65569              */
65570             'update',
65571
65572             /**
65573              * @event datachanged
65574              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
65575              * records, or updating the data in existing records
65576              * @param {Ext.data.Store} this The data store
65577              */
65578             'datachanged',
65579
65580             /**
65581              * @event beforeload
65582              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
65583              * action will be canceled.
65584              * @param {Ext.data.Store} store This Store
65585              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
65586              * load the Store
65587              */
65588             'beforeload',
65589
65590             /**
65591              * @event load
65592              * Fires whenever the store reads data from a remote data source.
65593              * @param {Ext.data.Store} this
65594              * @param {Ext.data.Model[]} records An array of records
65595              * @param {Boolean} successful True if the operation was successful.
65596              */
65597             'load',
65598             
65599             /**
65600              * @event write
65601              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
65602              * @param {Ext.data.Store} store This Store
65603              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
65604              * the write
65605              */
65606             'write',
65607
65608             /**
65609              * @event beforesync
65610              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
65611              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
65612              */
65613             'beforesync',
65614             /**
65615              * @event clear
65616              * Fired after the {@link #removeAll} method is called.
65617              * @param {Ext.data.Store} this
65618              */
65619             'clear'
65620         );
65621         
65622         Ext.apply(me, config);
65623         // don't use *config* anymore from here on... use *me* instead...
65624
65625         /**
65626          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
65627          * at which point this is cleared.
65628          * @private
65629          * @property {Ext.data.Model[]} removed
65630          */
65631         me.removed = [];
65632
65633         me.mixins.observable.constructor.apply(me, arguments);
65634         me.model = Ext.ModelManager.getModel(me.model);
65635
65636         /**
65637          * @property {Object} modelDefaults
65638          * @private
65639          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
65640          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
65641          * for examples. This should not need to be used by application developers.
65642          */
65643         Ext.applyIf(me, {
65644             modelDefaults: {}
65645         });
65646
65647         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
65648         if (!me.model && me.fields) {
65649             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
65650                 extend: 'Ext.data.Model',
65651                 fields: me.fields,
65652                 proxy: me.proxy || me.defaultProxyType
65653             });
65654
65655             delete me.fields;
65656
65657             me.implicitModel = true;
65658         }
65659         
65660
65661         //ensures that the Proxy is instantiated correctly
65662         me.setProxy(me.proxy || me.model.getProxy());
65663
65664         if (me.id && !me.storeId) {
65665             me.storeId = me.id;
65666             delete me.id;
65667         }
65668
65669         if (me.storeId) {
65670             Ext.data.StoreManager.register(me);
65671         }
65672         
65673         me.mixins.sortable.initSortable.call(me);        
65674         
65675         /**
65676          * @property {Ext.util.MixedCollection} filters
65677          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
65678          */
65679         filters = me.decodeFilters(me.filters);
65680         me.filters = Ext.create('Ext.util.MixedCollection');
65681         me.filters.addAll(filters);
65682     },
65683
65684     /**
65685      * Sets the Store's Proxy by string, config object or Proxy instance
65686      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
65687      * or an Ext.data.proxy.Proxy instance
65688      * @return {Ext.data.proxy.Proxy} The attached Proxy object
65689      */
65690     setProxy: function(proxy) {
65691         var me = this;
65692         
65693         if (proxy instanceof Ext.data.proxy.Proxy) {
65694             proxy.setModel(me.model);
65695         } else {
65696             if (Ext.isString(proxy)) {
65697                 proxy = {
65698                     type: proxy    
65699                 };
65700             }
65701             Ext.applyIf(proxy, {
65702                 model: me.model
65703             });
65704             
65705             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
65706         }
65707         
65708         me.proxy = proxy;
65709         
65710         return me.proxy;
65711     },
65712
65713     /**
65714      * Returns the proxy currently attached to this proxy instance
65715      * @return {Ext.data.proxy.Proxy} The Proxy instance
65716      */
65717     getProxy: function() {
65718         return this.proxy;
65719     },
65720
65721     //saves any phantom records
65722     create: function(data, options) {
65723         var me = this,
65724             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
65725             operation;
65726         
65727         options = options || {};
65728
65729         Ext.applyIf(options, {
65730             action : 'create',
65731             records: [instance]
65732         });
65733
65734         operation = Ext.create('Ext.data.Operation', options);
65735
65736         me.proxy.create(operation, me.onProxyWrite, me);
65737         
65738         return instance;
65739     },
65740
65741     read: function() {
65742         return this.load.apply(this, arguments);
65743     },
65744
65745     onProxyRead: Ext.emptyFn,
65746
65747     update: function(options) {
65748         var me = this,
65749             operation;
65750         options = options || {};
65751
65752         Ext.applyIf(options, {
65753             action : 'update',
65754             records: me.getUpdatedRecords()
65755         });
65756
65757         operation = Ext.create('Ext.data.Operation', options);
65758
65759         return me.proxy.update(operation, me.onProxyWrite, me);
65760     },
65761
65762     /**
65763      * @private
65764      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
65765      * the updates provided by the Proxy
65766      */
65767     onProxyWrite: function(operation) {
65768         var me = this,
65769             success = operation.wasSuccessful(),
65770             records = operation.getRecords();
65771
65772         switch (operation.action) {
65773             case 'create':
65774                 me.onCreateRecords(records, operation, success);
65775                 break;
65776             case 'update':
65777                 me.onUpdateRecords(records, operation, success);
65778                 break;
65779             case 'destroy':
65780                 me.onDestroyRecords(records, operation, success);
65781                 break;
65782         }
65783
65784         if (success) {
65785             me.fireEvent('write', me, operation);
65786             me.fireEvent('datachanged', me);
65787         }
65788         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
65789         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
65790     },
65791
65792
65793     //tells the attached proxy to destroy the given records
65794     destroy: function(options) {
65795         var me = this,
65796             operation;
65797             
65798         options = options || {};
65799
65800         Ext.applyIf(options, {
65801             action : 'destroy',
65802             records: me.getRemovedRecords()
65803         });
65804
65805         operation = Ext.create('Ext.data.Operation', options);
65806
65807         return me.proxy.destroy(operation, me.onProxyWrite, me);
65808     },
65809
65810     /**
65811      * @private
65812      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
65813      * to onProxyWrite.
65814      */
65815     onBatchOperationComplete: function(batch, operation) {
65816         return this.onProxyWrite(operation);
65817     },
65818
65819     /**
65820      * @private
65821      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
65822      * and updates the Store's internal data MixedCollection.
65823      */
65824     onBatchComplete: function(batch, operation) {
65825         var me = this,
65826             operations = batch.operations,
65827             length = operations.length,
65828             i;
65829
65830         me.suspendEvents();
65831
65832         for (i = 0; i < length; i++) {
65833             me.onProxyWrite(operations[i]);
65834         }
65835
65836         me.resumeEvents();
65837
65838         me.fireEvent('datachanged', me);
65839     },
65840
65841     onBatchException: function(batch, operation) {
65842         // //decide what to do... could continue with the next operation
65843         // batch.start();
65844         //
65845         // //or retry the last operation
65846         // batch.retry();
65847     },
65848
65849     /**
65850      * @private
65851      * Filter function for new records.
65852      */
65853     filterNew: function(item) {
65854         // only want phantom records that are valid
65855         return item.phantom === true && item.isValid();
65856     },
65857
65858     /**
65859      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
65860      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
65861      * @return {Ext.data.Model[]} The Model instances
65862      */
65863     getNewRecords: function() {
65864         return [];
65865     },
65866
65867     /**
65868      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
65869      * @return {Ext.data.Model[]} The updated Model instances
65870      */
65871     getUpdatedRecords: function() {
65872         return [];
65873     },
65874
65875     /**
65876      * @private
65877      * Filter function for updated records.
65878      */
65879     filterUpdated: function(item) {
65880         // only want dirty records, not phantoms that are valid
65881         return item.dirty === true && item.phantom !== true && item.isValid();
65882     },
65883
65884     /**
65885      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
65886      * @return {Ext.data.Model[]} The removed Model instances
65887      */
65888     getRemovedRecords: function() {
65889         return this.removed;
65890     },
65891
65892     filter: function(filters, value) {
65893
65894     },
65895
65896     /**
65897      * @private
65898      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
65899      * @param {Object[]} filters The filters array
65900      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
65901      */
65902     decodeFilters: function(filters) {
65903         if (!Ext.isArray(filters)) {
65904             if (filters === undefined) {
65905                 filters = [];
65906             } else {
65907                 filters = [filters];
65908             }
65909         }
65910
65911         var length = filters.length,
65912             Filter = Ext.util.Filter,
65913             config, i;
65914
65915         for (i = 0; i < length; i++) {
65916             config = filters[i];
65917
65918             if (!(config instanceof Filter)) {
65919                 Ext.apply(config, {
65920                     root: 'data'
65921                 });
65922
65923                 //support for 3.x style filters where a function can be defined as 'fn'
65924                 if (config.fn) {
65925                     config.filterFn = config.fn;
65926                 }
65927
65928                 //support a function to be passed as a filter definition
65929                 if (typeof config == 'function') {
65930                     config = {
65931                         filterFn: config
65932                     };
65933                 }
65934
65935                 filters[i] = new Filter(config);
65936             }
65937         }
65938
65939         return filters;
65940     },
65941
65942     clearFilter: function(supressEvent) {
65943
65944     },
65945
65946     isFiltered: function() {
65947
65948     },
65949
65950     filterBy: function(fn, scope) {
65951
65952     },
65953     
65954     /**
65955      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
65956      * and deleted records in the store, updating the Store's internal representation of the records
65957      * as each operation completes.
65958      */
65959     sync: function() {
65960         var me        = this,
65961             options   = {},
65962             toCreate  = me.getNewRecords(),
65963             toUpdate  = me.getUpdatedRecords(),
65964             toDestroy = me.getRemovedRecords(),
65965             needsSync = false;
65966
65967         if (toCreate.length > 0) {
65968             options.create = toCreate;
65969             needsSync = true;
65970         }
65971
65972         if (toUpdate.length > 0) {
65973             options.update = toUpdate;
65974             needsSync = true;
65975         }
65976
65977         if (toDestroy.length > 0) {
65978             options.destroy = toDestroy;
65979             needsSync = true;
65980         }
65981
65982         if (needsSync && me.fireEvent('beforesync', options) !== false) {
65983             me.proxy.batch(options, me.getBatchListeners());
65984         }
65985     },
65986
65987
65988     /**
65989      * @private
65990      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
65991      * This is broken out into a separate function to allow for customisation of the listeners
65992      * @return {Object} The listeners object
65993      */
65994     getBatchListeners: function() {
65995         var me = this,
65996             listeners = {
65997                 scope: me,
65998                 exception: me.onBatchException
65999             };
66000
66001         if (me.batchUpdateMode == 'operation') {
66002             listeners.operationcomplete = me.onBatchOperationComplete;
66003         } else {
66004             listeners.complete = me.onBatchComplete;
66005         }
66006
66007         return listeners;
66008     },
66009
66010     //deprecated, will be removed in 5.0
66011     save: function() {
66012         return this.sync.apply(this, arguments);
66013     },
66014
66015     /**
66016      * Loads the Store using its configured {@link #proxy}.
66017      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
66018      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
66019      */
66020     load: function(options) {
66021         var me = this,
66022             operation;
66023
66024         options = options || {};
66025
66026         Ext.applyIf(options, {
66027             action : 'read',
66028             filters: me.filters.items,
66029             sorters: me.getSorters()
66030         });
66031         
66032         operation = Ext.create('Ext.data.Operation', options);
66033
66034         if (me.fireEvent('beforeload', me, operation) !== false) {
66035             me.loading = true;
66036             me.proxy.read(operation, me.onProxyLoad, me);
66037         }
66038         
66039         return me;
66040     },
66041
66042     /**
66043      * @private
66044      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66045      * @param {Ext.data.Model} record The model instance that was edited
66046      */
66047     afterEdit : function(record) {
66048         var me = this;
66049         
66050         if (me.autoSync) {
66051             me.sync();
66052         }
66053         
66054         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
66055     },
66056
66057     /**
66058      * @private
66059      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
66060      * @param {Ext.data.Model} record The model instance that was edited
66061      */
66062     afterReject : function(record) {
66063         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
66064     },
66065
66066     /**
66067      * @private
66068      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66069      * @param {Ext.data.Model} record The model instance that was edited
66070      */
66071     afterCommit : function(record) {
66072         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
66073     },
66074
66075     clearData: Ext.emptyFn,
66076
66077     destroyStore: function() {
66078         var me = this;
66079         
66080         if (!me.isDestroyed) {
66081             if (me.storeId) {
66082                 Ext.data.StoreManager.unregister(me);
66083             }
66084             me.clearData();
66085             me.data = null;
66086             me.tree = null;
66087             // Ext.destroy(this.proxy);
66088             me.reader = me.writer = null;
66089             me.clearListeners();
66090             me.isDestroyed = true;
66091
66092             if (me.implicitModel) {
66093                 Ext.destroy(me.model);
66094             }
66095         }
66096     },
66097     
66098     doSort: function(sorterFn) {
66099         var me = this;
66100         if (me.remoteSort) {
66101             //the load function will pick up the new sorters and request the sorted data from the proxy
66102             me.load();
66103         } else {
66104             me.data.sortBy(sorterFn);
66105             me.fireEvent('datachanged', me);
66106         }
66107     },
66108
66109     getCount: Ext.emptyFn,
66110
66111     getById: Ext.emptyFn,
66112     
66113     /**
66114      * Removes all records from the store. This method does a "fast remove",
66115      * individual remove events are not called. The {@link #clear} event is
66116      * fired upon completion.
66117      * @method
66118      */
66119     removeAll: Ext.emptyFn,
66120     // individual substores should implement a "fast" remove
66121     // and fire a clear event afterwards
66122
66123     /**
66124      * Returns true if the Store is currently performing a load operation
66125      * @return {Boolean} True if the Store is currently loading
66126      */
66127     isLoading: function() {
66128         return !!this.loading;
66129      }
66130 });
66131
66132 /**
66133  * @class Ext.util.Grouper
66134  * @extends Ext.util.Sorter
66135
66136 Represents a single grouper that can be applied to a Store. The grouper works
66137 in the same fashion as the {@link Ext.util.Sorter}.
66138
66139  * @markdown
66140  */
66141  
66142 Ext.define('Ext.util.Grouper', {
66143
66144     /* Begin Definitions */
66145
66146     extend: 'Ext.util.Sorter',
66147
66148     /* End Definitions */
66149
66150     /**
66151      * Returns the value for grouping to be used.
66152      * @param {Ext.data.Model} instance The Model instance
66153      * @return {String} The group string for this model
66154      */
66155     getGroupString: function(instance) {
66156         return instance.get(this.property);
66157     }
66158 });
66159 /**
66160  * @author Ed Spencer
66161  * @class Ext.data.Store
66162  * @extends Ext.data.AbstractStore
66163  *
66164  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
66165  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
66166  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
66167  *
66168  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
66169  *
66170 <pre><code>
66171 // Set up a {@link Ext.data.Model model} to use in our Store
66172 Ext.define('User', {
66173     extend: 'Ext.data.Model',
66174     fields: [
66175         {name: 'firstName', type: 'string'},
66176         {name: 'lastName',  type: 'string'},
66177         {name: 'age',       type: 'int'},
66178         {name: 'eyeColor',  type: 'string'}
66179     ]
66180 });
66181
66182 var myStore = Ext.create('Ext.data.Store', {
66183     model: 'User',
66184     proxy: {
66185         type: 'ajax',
66186         url : '/users.json',
66187         reader: {
66188             type: 'json',
66189             root: 'users'
66190         }
66191     },
66192     autoLoad: true
66193 });
66194 </code></pre>
66195
66196  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
66197  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
66198  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
66199  *
66200  * <p><u>Inline data</u></p>
66201  *
66202  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
66203  * into Model instances:</p>
66204  *
66205 <pre><code>
66206 Ext.create('Ext.data.Store', {
66207     model: 'User',
66208     data : [
66209         {firstName: 'Ed',    lastName: 'Spencer'},
66210         {firstName: 'Tommy', lastName: 'Maintz'},
66211         {firstName: 'Aaron', lastName: 'Conran'},
66212         {firstName: 'Jamie', lastName: 'Avins'}
66213     ]
66214 });
66215 </code></pre>
66216  *
66217  * <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
66218  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
66219  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
66220  *
66221  * <p>Additional data can also be loaded locally using {@link #add}.</p>
66222  *
66223  * <p><u>Loading Nested Data</u></p>
66224  *
66225  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
66226  * 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
66227  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
66228  * docs for a full explanation:</p>
66229  *
66230 <pre><code>
66231 var store = Ext.create('Ext.data.Store', {
66232     autoLoad: true,
66233     model: "User",
66234     proxy: {
66235         type: 'ajax',
66236         url : 'users.json',
66237         reader: {
66238             type: 'json',
66239             root: 'users'
66240         }
66241     }
66242 });
66243 </code></pre>
66244  *
66245  * <p>Which would consume a response like this:</p>
66246  *
66247 <pre><code>
66248 {
66249     "users": [
66250         {
66251             "id": 1,
66252             "name": "Ed",
66253             "orders": [
66254                 {
66255                     "id": 10,
66256                     "total": 10.76,
66257                     "status": "invoiced"
66258                 },
66259                 {
66260                     "id": 11,
66261                     "total": 13.45,
66262                     "status": "shipped"
66263                 }
66264             ]
66265         }
66266     ]
66267 }
66268 </code></pre>
66269  *
66270  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
66271  *
66272  * <p><u>Filtering and Sorting</u></p>
66273  *
66274  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
66275  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
66276  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
66277  *
66278 <pre><code>
66279 var store = Ext.create('Ext.data.Store', {
66280     model: 'User',
66281     sorters: [
66282         {
66283             property : 'age',
66284             direction: 'DESC'
66285         },
66286         {
66287             property : 'firstName',
66288             direction: 'ASC'
66289         }
66290     ],
66291
66292     filters: [
66293         {
66294             property: 'firstName',
66295             value   : /Ed/
66296         }
66297     ]
66298 });
66299 </code></pre>
66300  *
66301  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
66302  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
66303  * perform these operations instead.</p>
66304  *
66305  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
66306  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
66307  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
66308  *
66309 <pre><code>
66310 store.filter('eyeColor', 'Brown');
66311 </code></pre>
66312  *
66313  * <p>Change the sorting at any time by calling {@link #sort}:</p>
66314  *
66315 <pre><code>
66316 store.sort('height', 'ASC');
66317 </code></pre>
66318  *
66319  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
66320  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
66321  * to the MixedCollection:</p>
66322  *
66323 <pre><code>
66324 store.sorters.add(new Ext.util.Sorter({
66325     property : 'shoeSize',
66326     direction: 'ASC'
66327 }));
66328
66329 store.sort();
66330 </code></pre>
66331  *
66332  * <p><u>Registering with StoreManager</u></p>
66333  *
66334  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
66335  * This makes it easy to reuse the same store in multiple views:</p>
66336  *
66337  <pre><code>
66338 //this store can be used several times
66339 Ext.create('Ext.data.Store', {
66340     model: 'User',
66341     storeId: 'usersStore'
66342 });
66343
66344 new Ext.List({
66345     store: 'usersStore',
66346
66347     //other config goes here
66348 });
66349
66350 new Ext.view.View({
66351     store: 'usersStore',
66352
66353     //other config goes here
66354 });
66355 </code></pre>
66356  *
66357  * <p><u>Further Reading</u></p>
66358  *
66359  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
66360  * pieces and how they fit together, see:</p>
66361  *
66362  * <ul style="list-style-type: disc; padding-left: 25px">
66363  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
66364  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
66365  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
66366  * </ul>
66367  *
66368  */
66369 Ext.define('Ext.data.Store', {
66370     extend: 'Ext.data.AbstractStore',
66371
66372     alias: 'store.store',
66373
66374     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
66375     uses: ['Ext.data.proxy.Memory'],
66376
66377     /**
66378      * @cfg {Boolean} remoteSort
66379      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
66380      */
66381     remoteSort: false,
66382
66383     /**
66384      * @cfg {Boolean} remoteFilter
66385      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
66386      */
66387     remoteFilter: false,
66388
66389     /**
66390      * @cfg {Boolean} remoteGroup
66391      * True if the grouping should apply on the server side, false if it is local only.  If the
66392      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
66393      * helper, automatically sending the grouping information to the server.
66394      */
66395     remoteGroup : false,
66396
66397     /**
66398      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
66399      * object or a Proxy instance - see {@link #setProxy} for details.
66400      */
66401
66402     /**
66403      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
66404      */
66405
66406     /**
66407      * @property {String} groupField
66408      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
66409      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
66410      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
66411      */
66412     groupField: undefined,
66413
66414     /**
66415      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
66416      * @property groupDir
66417      * @type String
66418      */
66419     groupDir: "ASC",
66420
66421     /**
66422      * @cfg {Number} pageSize
66423      * The number of records considered to form a 'page'. This is used to power the built-in
66424      * paging using the nextPage and previousPage functions. Defaults to 25.
66425      */
66426     pageSize: 25,
66427
66428     /**
66429      * The page that the Store has most recently loaded (see {@link #loadPage})
66430      * @property currentPage
66431      * @type Number
66432      */
66433     currentPage: 1,
66434
66435     /**
66436      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
66437      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
66438      * large data sets to be loaded one page at a time but rendered all together.
66439      */
66440     clearOnPageLoad: true,
66441
66442     /**
66443      * @property {Boolean} loading
66444      * True if the Store is currently loading via its Proxy
66445      * @private
66446      */
66447     loading: false,
66448
66449     /**
66450      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
66451      * causing the sorters to be reapplied after filtering. Defaults to true
66452      */
66453     sortOnFilter: true,
66454
66455     /**
66456      * @cfg {Boolean} buffered
66457      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
66458      * tell the store to pre-fetch records ahead of a time.
66459      */
66460     buffered: false,
66461
66462     /**
66463      * @cfg {Number} purgePageCount
66464      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
66465      * This option is only relevant when the {@link #buffered} option is set to true.
66466      */
66467     purgePageCount: 5,
66468
66469     isStore: true,
66470
66471     onClassExtended: function(cls, data) {
66472         var model = data.model;
66473
66474         if (typeof model == 'string') {
66475             var onBeforeClassCreated = data.onBeforeClassCreated;
66476
66477             data.onBeforeClassCreated = function(cls, data) {
66478                 var me = this;
66479
66480                 Ext.require(model, function() {
66481                     onBeforeClassCreated.call(me, cls, data);
66482                 });
66483             };
66484         }
66485     },
66486
66487     /**
66488      * Creates the store.
66489      * @param {Object} config (optional) Config object
66490      */
66491     constructor: function(config) {
66492         // Clone the config so we don't modify the original config object
66493         config = Ext.Object.merge({}, config);
66494
66495         var me = this,
66496             groupers = config.groupers || me.groupers,
66497             groupField = config.groupField || me.groupField,
66498             proxy,
66499             data;
66500
66501         if (config.buffered || me.buffered) {
66502             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
66503                 return record.index;
66504             });
66505             me.pendingRequests = [];
66506             me.pagesRequested = [];
66507
66508             me.sortOnLoad = false;
66509             me.filterOnLoad = false;
66510         }
66511
66512         me.addEvents(
66513             /**
66514              * @event beforeprefetch
66515              * Fires before a prefetch occurs. Return false to cancel.
66516              * @param {Ext.data.Store} this
66517              * @param {Ext.data.Operation} operation The associated operation
66518              */
66519             'beforeprefetch',
66520             /**
66521              * @event groupchange
66522              * Fired whenever the grouping in the grid changes
66523              * @param {Ext.data.Store} store The store
66524              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
66525              */
66526             'groupchange',
66527             /**
66528              * @event load
66529              * Fires whenever records have been prefetched
66530              * @param {Ext.data.Store} this
66531              * @param {Ext.util.Grouper[]} records An array of records
66532              * @param {Boolean} successful True if the operation was successful.
66533              * @param {Ext.data.Operation} operation The associated operation
66534              */
66535             'prefetch'
66536         );
66537         data = config.data || me.data;
66538
66539         /**
66540          * The MixedCollection that holds this store's local cache of records
66541          * @property data
66542          * @type Ext.util.MixedCollection
66543          */
66544         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
66545             return record.internalId;
66546         });
66547
66548         if (data) {
66549             me.inlineData = data;
66550             delete config.data;
66551         }
66552
66553         if (!groupers && groupField) {
66554             groupers = [{
66555                 property : groupField,
66556                 direction: config.groupDir || me.groupDir
66557             }];
66558         }
66559         delete config.groupers;
66560
66561         /**
66562          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
66563          * @property groupers
66564          * @type Ext.util.MixedCollection
66565          */
66566         me.groupers = Ext.create('Ext.util.MixedCollection');
66567         me.groupers.addAll(me.decodeGroupers(groupers));
66568
66569         this.callParent([config]);
66570         // don't use *config* anymore from here on... use *me* instead...
66571
66572         if (me.groupers.items.length) {
66573             me.sort(me.groupers.items, 'prepend', false);
66574         }
66575
66576         proxy = me.proxy;
66577         data = me.inlineData;
66578
66579         if (data) {
66580             if (proxy instanceof Ext.data.proxy.Memory) {
66581                 proxy.data = data;
66582                 me.read();
66583             } else {
66584                 me.add.apply(me, data);
66585             }
66586
66587             me.sort();
66588             delete me.inlineData;
66589         } else if (me.autoLoad) {
66590             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
66591             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
66592             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
66593         }
66594     },
66595
66596     onBeforeSort: function() {
66597         var groupers = this.groupers;
66598         if (groupers.getCount() > 0) {
66599             this.sort(groupers.items, 'prepend', false);
66600         }
66601     },
66602
66603     /**
66604      * @private
66605      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
66606      * @param {Object[]} groupers The groupers array
66607      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
66608      */
66609     decodeGroupers: function(groupers) {
66610         if (!Ext.isArray(groupers)) {
66611             if (groupers === undefined) {
66612                 groupers = [];
66613             } else {
66614                 groupers = [groupers];
66615             }
66616         }
66617
66618         var length  = groupers.length,
66619             Grouper = Ext.util.Grouper,
66620             config, i;
66621
66622         for (i = 0; i < length; i++) {
66623             config = groupers[i];
66624
66625             if (!(config instanceof Grouper)) {
66626                 if (Ext.isString(config)) {
66627                     config = {
66628                         property: config
66629                     };
66630                 }
66631
66632                 Ext.applyIf(config, {
66633                     root     : 'data',
66634                     direction: "ASC"
66635                 });
66636
66637                 //support for 3.x style sorters where a function can be defined as 'fn'
66638                 if (config.fn) {
66639                     config.sorterFn = config.fn;
66640                 }
66641
66642                 //support a function to be passed as a sorter definition
66643                 if (typeof config == 'function') {
66644                     config = {
66645                         sorterFn: config
66646                     };
66647                 }
66648
66649                 groupers[i] = new Grouper(config);
66650             }
66651         }
66652
66653         return groupers;
66654     },
66655
66656     /**
66657      * Group data in the store
66658      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
66659      * or an Array of grouper configurations.
66660      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
66661      */
66662     group: function(groupers, direction) {
66663         var me = this,
66664             hasNew = false,
66665             grouper,
66666             newGroupers;
66667
66668         if (Ext.isArray(groupers)) {
66669             newGroupers = groupers;
66670         } else if (Ext.isObject(groupers)) {
66671             newGroupers = [groupers];
66672         } else if (Ext.isString(groupers)) {
66673             grouper = me.groupers.get(groupers);
66674
66675             if (!grouper) {
66676                 grouper = {
66677                     property : groupers,
66678                     direction: direction
66679                 };
66680                 newGroupers = [grouper];
66681             } else if (direction === undefined) {
66682                 grouper.toggle();
66683             } else {
66684                 grouper.setDirection(direction);
66685             }
66686         }
66687
66688         if (newGroupers && newGroupers.length) {
66689             hasNew = true;
66690             newGroupers = me.decodeGroupers(newGroupers);
66691             me.groupers.clear();
66692             me.groupers.addAll(newGroupers);
66693         }
66694
66695         if (me.remoteGroup) {
66696             me.load({
66697                 scope: me,
66698                 callback: me.fireGroupChange
66699             });
66700         } else {
66701             // need to explicitly force a sort if we have groupers
66702             me.sort(null, null, null, hasNew);
66703             me.fireGroupChange();
66704         }
66705     },
66706
66707     /**
66708      * Clear any groupers in the store
66709      */
66710     clearGrouping: function(){
66711         var me = this;
66712         // Clear any groupers we pushed on to the sorters
66713         me.groupers.each(function(grouper){
66714             me.sorters.remove(grouper);
66715         });
66716         me.groupers.clear();
66717         if (me.remoteGroup) {
66718             me.load({
66719                 scope: me,
66720                 callback: me.fireGroupChange
66721             });
66722         } else {
66723             me.sort();
66724             me.fireEvent('groupchange', me, me.groupers);
66725         }
66726     },
66727
66728     /**
66729      * Checks if the store is currently grouped
66730      * @return {Boolean} True if the store is grouped.
66731      */
66732     isGrouped: function() {
66733         return this.groupers.getCount() > 0;
66734     },
66735
66736     /**
66737      * Fires the groupchange event. Abstracted out so we can use it
66738      * as a callback
66739      * @private
66740      */
66741     fireGroupChange: function(){
66742         this.fireEvent('groupchange', this, this.groupers);
66743     },
66744
66745     /**
66746      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
66747      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
66748 <pre><code>
66749 var myStore = Ext.create('Ext.data.Store', {
66750     groupField: 'color',
66751     groupDir  : 'DESC'
66752 });
66753
66754 myStore.getGroups(); //returns:
66755 [
66756     {
66757         name: 'yellow',
66758         children: [
66759             //all records where the color field is 'yellow'
66760         ]
66761     },
66762     {
66763         name: 'red',
66764         children: [
66765             //all records where the color field is 'red'
66766         ]
66767     }
66768 ]
66769 </code></pre>
66770      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
66771      * @return {Object/Object[]} The grouped data
66772      */
66773     getGroups: function(requestGroupString) {
66774         var records = this.data.items,
66775             length = records.length,
66776             groups = [],
66777             pointers = {},
66778             record,
66779             groupStr,
66780             group,
66781             i;
66782
66783         for (i = 0; i < length; i++) {
66784             record = records[i];
66785             groupStr = this.getGroupString(record);
66786             group = pointers[groupStr];
66787
66788             if (group === undefined) {
66789                 group = {
66790                     name: groupStr,
66791                     children: []
66792                 };
66793
66794                 groups.push(group);
66795                 pointers[groupStr] = group;
66796             }
66797
66798             group.children.push(record);
66799         }
66800
66801         return requestGroupString ? pointers[requestGroupString] : groups;
66802     },
66803
66804     /**
66805      * @private
66806      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
66807      * matching a certain group.
66808      */
66809     getGroupsForGrouper: function(records, grouper) {
66810         var length = records.length,
66811             groups = [],
66812             oldValue,
66813             newValue,
66814             record,
66815             group,
66816             i;
66817
66818         for (i = 0; i < length; i++) {
66819             record = records[i];
66820             newValue = grouper.getGroupString(record);
66821
66822             if (newValue !== oldValue) {
66823                 group = {
66824                     name: newValue,
66825                     grouper: grouper,
66826                     records: []
66827                 };
66828                 groups.push(group);
66829             }
66830
66831             group.records.push(record);
66832
66833             oldValue = newValue;
66834         }
66835
66836         return groups;
66837     },
66838
66839     /**
66840      * @private
66841      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
66842      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
66843      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
66844      * @param {Ext.data.Model[]} records The set or subset of records to group
66845      * @param {Number} grouperIndex The grouper index to retrieve
66846      * @return {Object[]} The grouped records
66847      */
66848     getGroupsForGrouperIndex: function(records, grouperIndex) {
66849         var me = this,
66850             groupers = me.groupers,
66851             grouper = groupers.getAt(grouperIndex),
66852             groups = me.getGroupsForGrouper(records, grouper),
66853             length = groups.length,
66854             i;
66855
66856         if (grouperIndex + 1 < groupers.length) {
66857             for (i = 0; i < length; i++) {
66858                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
66859             }
66860         }
66861
66862         for (i = 0; i < length; i++) {
66863             groups[i].depth = grouperIndex;
66864         }
66865
66866         return groups;
66867     },
66868
66869     /**
66870      * @private
66871      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
66872      * this case grouping by genre and then author in a fictional books dataset):</p>
66873 <pre><code>
66874 [
66875     {
66876         name: 'Fantasy',
66877         depth: 0,
66878         records: [
66879             //book1, book2, book3, book4
66880         ],
66881         children: [
66882             {
66883                 name: 'Rowling',
66884                 depth: 1,
66885                 records: [
66886                     //book1, book2
66887                 ]
66888             },
66889             {
66890                 name: 'Tolkein',
66891                 depth: 1,
66892                 records: [
66893                     //book3, book4
66894                 ]
66895             }
66896         ]
66897     }
66898 ]
66899 </code></pre>
66900      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
66901      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
66902      * (defaults to true)
66903      * @return {Object[]} The group data
66904      */
66905     getGroupData: function(sort) {
66906         var me = this;
66907         if (sort !== false) {
66908             me.sort();
66909         }
66910
66911         return me.getGroupsForGrouperIndex(me.data.items, 0);
66912     },
66913
66914     /**
66915      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
66916      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
66917      * group by the first letter of a model's 'name' field, use the following code:</p>
66918 <pre><code>
66919 Ext.create('Ext.data.Store', {
66920     groupDir: 'ASC',
66921     getGroupString: function(instance) {
66922         return instance.get('name')[0];
66923     }
66924 });
66925 </code></pre>
66926      * @param {Ext.data.Model} instance The model instance
66927      * @return {String} The string to compare when forming groups
66928      */
66929     getGroupString: function(instance) {
66930         var group = this.groupers.first();
66931         if (group) {
66932             return instance.get(group.property);
66933         }
66934         return '';
66935     },
66936     /**
66937      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
66938      * See also <code>{@link #add}</code>.
66939      * @param {Number} index The start index at which to insert the passed Records.
66940      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
66941      */
66942     insert: function(index, records) {
66943         var me = this,
66944             sync = false,
66945             i,
66946             record,
66947             len;
66948
66949         records = [].concat(records);
66950         for (i = 0, len = records.length; i < len; i++) {
66951             record = me.createModel(records[i]);
66952             record.set(me.modelDefaults);
66953             // reassign the model in the array in case it wasn't created yet
66954             records[i] = record;
66955
66956             me.data.insert(index + i, record);
66957             record.join(me);
66958
66959             sync = sync || record.phantom === true;
66960         }
66961
66962         if (me.snapshot) {
66963             me.snapshot.addAll(records);
66964         }
66965
66966         me.fireEvent('add', me, records, index);
66967         me.fireEvent('datachanged', me);
66968         if (me.autoSync && sync) {
66969             me.sync();
66970         }
66971     },
66972
66973     /**
66974      * Adds Model instance to the Store. This method accepts either:
66975      *
66976      * - An array of Model instances or Model configuration objects.
66977      * - Any number of Model instance or Model configuration object arguments.
66978      *
66979      * The new Model instances will be added at the end of the existing collection.
66980      *
66981      * Sample usage:
66982      *
66983      *     myStore.add({some: 'data'}, {some: 'other data'});
66984      *
66985      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
66986      * or Model configuration objects, or variable number of Model instance or config arguments.
66987      * @return {Ext.data.Model[]} The model instances that were added
66988      */
66989     add: function(records) {
66990         //accept both a single-argument array of records, or any number of record arguments
66991         if (!Ext.isArray(records)) {
66992             records = Array.prototype.slice.apply(arguments);
66993         }
66994
66995         var me = this,
66996             i = 0,
66997             length = records.length,
66998             record;
66999
67000         for (; i < length; i++) {
67001             record = me.createModel(records[i]);
67002             // reassign the model in the array in case it wasn't created yet
67003             records[i] = record;
67004         }
67005
67006         me.insert(me.data.length, records);
67007
67008         return records;
67009     },
67010
67011     /**
67012      * Converts a literal to a model, if it's not a model already
67013      * @private
67014      * @param record {Ext.data.Model/Object} The record to create
67015      * @return {Ext.data.Model}
67016      */
67017     createModel: function(record) {
67018         if (!record.isModel) {
67019             record = Ext.ModelManager.create(record, this.model);
67020         }
67021
67022         return record;
67023     },
67024
67025     /**
67026      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
67027      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
67028      * Returning <tt>false</tt> aborts and exits the iteration.
67029      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
67030      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
67031      */
67032     each: function(fn, scope) {
67033         this.data.each(fn, scope);
67034     },
67035
67036     /**
67037      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
67038      * 'datachanged' event after removal.
67039      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
67040      */
67041     remove: function(records, /* private */ isMove) {
67042         if (!Ext.isArray(records)) {
67043             records = [records];
67044         }
67045
67046         /*
67047          * Pass the isMove parameter if we know we're going to be re-inserting this record
67048          */
67049         isMove = isMove === true;
67050         var me = this,
67051             sync = false,
67052             i = 0,
67053             length = records.length,
67054             isPhantom,
67055             index,
67056             record;
67057
67058         for (; i < length; i++) {
67059             record = records[i];
67060             index = me.data.indexOf(record);
67061
67062             if (me.snapshot) {
67063                 me.snapshot.remove(record);
67064             }
67065
67066             if (index > -1) {
67067                 isPhantom = record.phantom === true;
67068                 if (!isMove && !isPhantom) {
67069                     // don't push phantom records onto removed
67070                     me.removed.push(record);
67071                 }
67072
67073                 record.unjoin(me);
67074                 me.data.remove(record);
67075                 sync = sync || !isPhantom;
67076
67077                 me.fireEvent('remove', me, record, index);
67078             }
67079         }
67080
67081         me.fireEvent('datachanged', me);
67082         if (!isMove && me.autoSync && sync) {
67083             me.sync();
67084         }
67085     },
67086
67087     /**
67088      * Removes the model instance at the given index
67089      * @param {Number} index The record index
67090      */
67091     removeAt: function(index) {
67092         var record = this.getAt(index);
67093
67094         if (record) {
67095             this.remove(record);
67096         }
67097     },
67098
67099     /**
67100      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
67101      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
67102      * instances into the Store and calling an optional callback if required. Example usage:</p>
67103      *
67104 <pre><code>
67105 store.load({
67106     scope   : this,
67107     callback: function(records, operation, success) {
67108         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
67109         console.log(records);
67110     }
67111 });
67112 </code></pre>
67113      *
67114      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
67115      *
67116 <pre><code>
67117 store.load(function(records, operation, success) {
67118     console.log('loaded records');
67119 });
67120 </code></pre>
67121      *
67122      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67123      */
67124     load: function(options) {
67125         var me = this;
67126
67127         options = options || {};
67128
67129         if (Ext.isFunction(options)) {
67130             options = {
67131                 callback: options
67132             };
67133         }
67134
67135         Ext.applyIf(options, {
67136             groupers: me.groupers.items,
67137             page: me.currentPage,
67138             start: (me.currentPage - 1) * me.pageSize,
67139             limit: me.pageSize,
67140             addRecords: false
67141         });
67142
67143         return me.callParent([options]);
67144     },
67145
67146     /**
67147      * @private
67148      * Called internally when a Proxy has completed a load request
67149      */
67150     onProxyLoad: function(operation) {
67151         var me = this,
67152             resultSet = operation.getResultSet(),
67153             records = operation.getRecords(),
67154             successful = operation.wasSuccessful();
67155
67156         if (resultSet) {
67157             me.totalCount = resultSet.total;
67158         }
67159
67160         if (successful) {
67161             me.loadRecords(records, operation);
67162         }
67163
67164         me.loading = false;
67165         me.fireEvent('load', me, records, successful);
67166
67167         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
67168         //People are definitely using this so can't deprecate safely until 2.x
67169         me.fireEvent('read', me, records, operation.wasSuccessful());
67170
67171         //this is a callback that would have been passed to the 'read' function and is optional
67172         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67173     },
67174
67175     /**
67176      * Create any new records when a write is returned from the server.
67177      * @private
67178      * @param {Ext.data.Model[]} records The array of new records
67179      * @param {Ext.data.Operation} operation The operation that just completed
67180      * @param {Boolean} success True if the operation was successful
67181      */
67182     onCreateRecords: function(records, operation, success) {
67183         if (success) {
67184             var i = 0,
67185                 data = this.data,
67186                 snapshot = this.snapshot,
67187                 length = records.length,
67188                 originalRecords = operation.records,
67189                 record,
67190                 original,
67191                 index;
67192
67193             /*
67194              * Loop over each record returned from the server. Assume they are
67195              * returned in order of how they were sent. If we find a matching
67196              * record, replace it with the newly created one.
67197              */
67198             for (; i < length; ++i) {
67199                 record = records[i];
67200                 original = originalRecords[i];
67201                 if (original) {
67202                     index = data.indexOf(original);
67203                     if (index > -1) {
67204                         data.removeAt(index);
67205                         data.insert(index, record);
67206                     }
67207                     if (snapshot) {
67208                         index = snapshot.indexOf(original);
67209                         if (index > -1) {
67210                             snapshot.removeAt(index);
67211                             snapshot.insert(index, record);
67212                         }
67213                     }
67214                     record.phantom = false;
67215                     record.join(this);
67216                 }
67217             }
67218         }
67219     },
67220
67221     /**
67222      * Update any records when a write is returned from the server.
67223      * @private
67224      * @param {Ext.data.Model[]} records The array of updated records
67225      * @param {Ext.data.Operation} operation The operation that just completed
67226      * @param {Boolean} success True if the operation was successful
67227      */
67228     onUpdateRecords: function(records, operation, success){
67229         if (success) {
67230             var i = 0,
67231                 length = records.length,
67232                 data = this.data,
67233                 snapshot = this.snapshot,
67234                 record;
67235
67236             for (; i < length; ++i) {
67237                 record = records[i];
67238                 data.replace(record);
67239                 if (snapshot) {
67240                     snapshot.replace(record);
67241                 }
67242                 record.join(this);
67243             }
67244         }
67245     },
67246
67247     /**
67248      * Remove any records when a write is returned from the server.
67249      * @private
67250      * @param {Ext.data.Model[]} records The array of removed records
67251      * @param {Ext.data.Operation} operation The operation that just completed
67252      * @param {Boolean} success True if the operation was successful
67253      */
67254     onDestroyRecords: function(records, operation, success){
67255         if (success) {
67256             var me = this,
67257                 i = 0,
67258                 length = records.length,
67259                 data = me.data,
67260                 snapshot = me.snapshot,
67261                 record;
67262
67263             for (; i < length; ++i) {
67264                 record = records[i];
67265                 record.unjoin(me);
67266                 data.remove(record);
67267                 if (snapshot) {
67268                     snapshot.remove(record);
67269                 }
67270             }
67271             me.removed = [];
67272         }
67273     },
67274
67275     //inherit docs
67276     getNewRecords: function() {
67277         return this.data.filterBy(this.filterNew).items;
67278     },
67279
67280     //inherit docs
67281     getUpdatedRecords: function() {
67282         return this.data.filterBy(this.filterUpdated).items;
67283     },
67284
67285     /**
67286      * Filters the loaded set of records by a given set of filters.
67287      *
67288      * Filtering by single field:
67289      *
67290      *     store.filter("email", /\.com$/);
67291      *
67292      * Using multiple filters:
67293      *
67294      *     store.filter([
67295      *         {property: "email", value: /\.com$/},
67296      *         {filterFn: function(item) { return item.get("age") > 10; }}
67297      *     ]);
67298      *
67299      * Using Ext.util.Filter instances instead of config objects
67300      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
67301      *
67302      *     store.filter([
67303      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
67304      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
67305      *     ]);
67306      *
67307      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
67308      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
67309      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
67310      * pass in a property string
67311      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
67312      */
67313     filter: function(filters, value) {
67314         if (Ext.isString(filters)) {
67315             filters = {
67316                 property: filters,
67317                 value: value
67318             };
67319         }
67320
67321         var me = this,
67322             decoded = me.decodeFilters(filters),
67323             i = 0,
67324             doLocalSort = me.sortOnFilter && !me.remoteSort,
67325             length = decoded.length;
67326
67327         for (; i < length; i++) {
67328             me.filters.replace(decoded[i]);
67329         }
67330
67331         if (me.remoteFilter) {
67332             //the load function will pick up the new filters and request the filtered data from the proxy
67333             me.load();
67334         } else {
67335             /**
67336              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
67337              * records when a filter is removed or changed
67338              * @property snapshot
67339              * @type Ext.util.MixedCollection
67340              */
67341             if (me.filters.getCount()) {
67342                 me.snapshot = me.snapshot || me.data.clone();
67343                 me.data = me.data.filter(me.filters.items);
67344
67345                 if (doLocalSort) {
67346                     me.sort();
67347                 }
67348                 // fire datachanged event if it hasn't already been fired by doSort
67349                 if (!doLocalSort || me.sorters.length < 1) {
67350                     me.fireEvent('datachanged', me);
67351                 }
67352             }
67353         }
67354     },
67355
67356     /**
67357      * Revert to a view of the Record cache with no filtering applied.
67358      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
67359      * {@link #datachanged} event.
67360      */
67361     clearFilter: function(suppressEvent) {
67362         var me = this;
67363
67364         me.filters.clear();
67365
67366         if (me.remoteFilter) {
67367             me.load();
67368         } else if (me.isFiltered()) {
67369             me.data = me.snapshot.clone();
67370             delete me.snapshot;
67371
67372             if (suppressEvent !== true) {
67373                 me.fireEvent('datachanged', me);
67374             }
67375         }
67376     },
67377
67378     /**
67379      * Returns true if this store is currently filtered
67380      * @return {Boolean}
67381      */
67382     isFiltered: function() {
67383         var snapshot = this.snapshot;
67384         return !! snapshot && snapshot !== this.data;
67385     },
67386
67387     /**
67388      * Filter by a function. The specified function will be called for each
67389      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
67390      * otherwise it is filtered out.
67391      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67392      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67393      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67394      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67395      * </ul>
67396      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67397      */
67398     filterBy: function(fn, scope) {
67399         var me = this;
67400
67401         me.snapshot = me.snapshot || me.data.clone();
67402         me.data = me.queryBy(fn, scope || me);
67403         me.fireEvent('datachanged', me);
67404     },
67405
67406     /**
67407      * Query the cached records in this Store using a filtering function. The specified function
67408      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
67409      * included in the results.
67410      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67411      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67412      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67413      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67414      * </ul>
67415      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67416      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
67417      **/
67418     queryBy: function(fn, scope) {
67419         var me = this,
67420         data = me.snapshot || me.data;
67421         return data.filterBy(fn, scope || me);
67422     },
67423
67424     /**
67425      * Loads an array of data straight into the Store.
67426      * 
67427      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
67428      * processed by a reader). If your data requires processing to decode the data structure, use a
67429      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
67430      * 
67431      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
67432      * into model instances.
67433      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67434      * to remove the old ones first.
67435      */
67436     loadData: function(data, append) {
67437         var model = this.model,
67438             length = data.length,
67439             newData = [],
67440             i,
67441             record;
67442
67443         //make sure each data element is an Ext.data.Model instance
67444         for (i = 0; i < length; i++) {
67445             record = data[i];
67446
67447             if (!(record instanceof Ext.data.Model)) {
67448                 record = Ext.ModelManager.create(record, model);
67449             }
67450             newData.push(record);
67451         }
67452
67453         this.loadRecords(newData, {addRecords: append});
67454     },
67455
67456
67457     /**
67458      * Loads data via the bound Proxy's reader
67459      *
67460      * Use this method if you are attempting to load data and want to utilize the configured data reader.
67461      *
67462      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
67463      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67464      * to remove the old ones first.
67465      */
67466     loadRawData : function(data, append) {
67467          var me      = this,
67468              result  = me.proxy.reader.read(data),
67469              records = result.records;
67470
67471          if (result.success) {
67472              me.loadRecords(records, { addRecords: append });
67473              me.fireEvent('load', me, records, true);
67474          }
67475      },
67476
67477
67478     /**
67479      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
67480      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
67481      * @param {Ext.data.Model[]} records The array of records to load
67482      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
67483      */
67484     loadRecords: function(records, options) {
67485         var me     = this,
67486             i      = 0,
67487             length = records.length;
67488
67489         options = options || {};
67490
67491
67492         if (!options.addRecords) {
67493             delete me.snapshot;
67494             me.clearData();
67495         }
67496
67497         me.data.addAll(records);
67498
67499         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
67500         for (; i < length; i++) {
67501             if (options.start !== undefined) {
67502                 records[i].index = options.start + i;
67503
67504             }
67505             records[i].join(me);
67506         }
67507
67508         /*
67509          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
67510          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
67511          * datachanged event is fired by the call to this.add, above.
67512          */
67513         me.suspendEvents();
67514
67515         if (me.filterOnLoad && !me.remoteFilter) {
67516             me.filter();
67517         }
67518
67519         if (me.sortOnLoad && !me.remoteSort) {
67520             me.sort();
67521         }
67522
67523         me.resumeEvents();
67524         me.fireEvent('datachanged', me, records);
67525     },
67526
67527     // PAGING METHODS
67528     /**
67529      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
67530      * load operation, passing in calculated 'start' and 'limit' params
67531      * @param {Number} page The number of the page to load
67532      * @param {Object} options See options for {@link #load}
67533      */
67534     loadPage: function(page, options) {
67535         var me = this;
67536         options = Ext.apply({}, options);
67537
67538         me.currentPage = page;
67539
67540         me.read(Ext.applyIf(options, {
67541             page: page,
67542             start: (page - 1) * me.pageSize,
67543             limit: me.pageSize,
67544             addRecords: !me.clearOnPageLoad
67545         }));
67546     },
67547
67548     /**
67549      * Loads the next 'page' in the current data set
67550      * @param {Object} options See options for {@link #load}
67551      */
67552     nextPage: function(options) {
67553         this.loadPage(this.currentPage + 1, options);
67554     },
67555
67556     /**
67557      * Loads the previous 'page' in the current data set
67558      * @param {Object} options See options for {@link #load}
67559      */
67560     previousPage: function(options) {
67561         this.loadPage(this.currentPage - 1, options);
67562     },
67563
67564     // private
67565     clearData: function() {
67566         var me = this;
67567         me.data.each(function(record) {
67568             record.unjoin(me);
67569         });
67570
67571         me.data.clear();
67572     },
67573
67574     // Buffering
67575     /**
67576      * Prefetches data into the store using its configured {@link #proxy}.
67577      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67578      * See {@link #load}
67579      */
67580     prefetch: function(options) {
67581         var me = this,
67582             operation,
67583             requestId = me.getRequestId();
67584
67585         options = options || {};
67586
67587         Ext.applyIf(options, {
67588             action : 'read',
67589             filters: me.filters.items,
67590             sorters: me.sorters.items,
67591             requestId: requestId
67592         });
67593         me.pendingRequests.push(requestId);
67594
67595         operation = Ext.create('Ext.data.Operation', options);
67596
67597         // HACK to implement loadMask support.
67598         //if (operation.blocking) {
67599         //    me.fireEvent('beforeload', me, operation);
67600         //}
67601         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
67602             me.loading = true;
67603             me.proxy.read(operation, me.onProxyPrefetch, me);
67604         }
67605
67606         return me;
67607     },
67608
67609     /**
67610      * Prefetches a page of data.
67611      * @param {Number} page The page to prefetch
67612      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67613      * See {@link #load}
67614      */
67615     prefetchPage: function(page, options) {
67616         var me = this,
67617             pageSize = me.pageSize,
67618             start = (page - 1) * me.pageSize,
67619             end = start + pageSize;
67620
67621         // Currently not requesting this page and range isn't already satisified
67622         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
67623             options = options || {};
67624             me.pagesRequested.push(page);
67625             Ext.applyIf(options, {
67626                 page : page,
67627                 start: start,
67628                 limit: pageSize,
67629                 callback: me.onWaitForGuarantee,
67630                 scope: me
67631             });
67632
67633             me.prefetch(options);
67634         }
67635
67636     },
67637
67638     /**
67639      * Returns a unique requestId to track requests.
67640      * @private
67641      */
67642     getRequestId: function() {
67643         this.requestSeed = this.requestSeed || 1;
67644         return this.requestSeed++;
67645     },
67646
67647     /**
67648      * Called after the configured proxy completes a prefetch operation.
67649      * @private
67650      * @param {Ext.data.Operation} operation The operation that completed
67651      */
67652     onProxyPrefetch: function(operation) {
67653         var me         = this,
67654             resultSet  = operation.getResultSet(),
67655             records    = operation.getRecords(),
67656
67657             successful = operation.wasSuccessful();
67658
67659         if (resultSet) {
67660             me.totalCount = resultSet.total;
67661             me.fireEvent('totalcountchange', me.totalCount);
67662         }
67663
67664         if (successful) {
67665             me.cacheRecords(records, operation);
67666         }
67667         Ext.Array.remove(me.pendingRequests, operation.requestId);
67668         if (operation.page) {
67669             Ext.Array.remove(me.pagesRequested, operation.page);
67670         }
67671
67672         me.loading = false;
67673         me.fireEvent('prefetch', me, records, successful, operation);
67674
67675         // HACK to support loadMask
67676         if (operation.blocking) {
67677             me.fireEvent('load', me, records, successful);
67678         }
67679
67680         //this is a callback that would have been passed to the 'read' function and is optional
67681         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67682     },
67683
67684     /**
67685      * Caches the records in the prefetch and stripes them with their server-side
67686      * index.
67687      * @private
67688      * @param {Ext.data.Model[]} records The records to cache
67689      * @param {Ext.data.Operation} The associated operation
67690      */
67691     cacheRecords: function(records, operation) {
67692         var me     = this,
67693             i      = 0,
67694             length = records.length,
67695             start  = operation ? operation.start : 0;
67696
67697         if (!Ext.isDefined(me.totalCount)) {
67698             me.totalCount = records.length;
67699             me.fireEvent('totalcountchange', me.totalCount);
67700         }
67701
67702         for (; i < length; i++) {
67703             // this is the true index, not the viewIndex
67704             records[i].index = start + i;
67705         }
67706
67707         me.prefetchData.addAll(records);
67708         if (me.purgePageCount) {
67709             me.purgeRecords();
67710         }
67711
67712     },
67713
67714
67715     /**
67716      * Purge the least recently used records in the prefetch if the purgeCount
67717      * has been exceeded.
67718      */
67719     purgeRecords: function() {
67720         var me = this,
67721             prefetchCount = me.prefetchData.getCount(),
67722             purgeCount = me.purgePageCount * me.pageSize,
67723             numRecordsToPurge = prefetchCount - purgeCount - 1,
67724             i = 0;
67725
67726         for (; i <= numRecordsToPurge; i++) {
67727             me.prefetchData.removeAt(0);
67728         }
67729     },
67730
67731     /**
67732      * Determines if the range has already been satisfied in the prefetchData.
67733      * @private
67734      * @param {Number} start The start index
67735      * @param {Number} end The end index in the range
67736      */
67737     rangeSatisfied: function(start, end) {
67738         var me = this,
67739             i = start,
67740             satisfied = true;
67741
67742         for (; i < end; i++) {
67743             if (!me.prefetchData.getByKey(i)) {
67744                 satisfied = false;
67745                 break;
67746             }
67747         }
67748         return satisfied;
67749     },
67750
67751     /**
67752      * Determines the page from a record index
67753      * @param {Number} index The record index
67754      * @return {Number} The page the record belongs to
67755      */
67756     getPageFromRecordIndex: function(index) {
67757         return Math.floor(index / this.pageSize) + 1;
67758     },
67759
67760     /**
67761      * Handles a guaranteed range being loaded
67762      * @private
67763      */
67764     onGuaranteedRange: function() {
67765         var me = this,
67766             totalCount = me.getTotalCount(),
67767             start = me.requestStart,
67768             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
67769             range = [],
67770             record,
67771             i = start;
67772
67773         end = Math.max(0, end);
67774
67775
67776         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
67777             me.guaranteedStart = start;
67778             me.guaranteedEnd = end;
67779
67780             for (; i <= end; i++) {
67781                 record = me.prefetchData.getByKey(i);
67782                 if (record) {
67783                     range.push(record);
67784                 }
67785             }
67786             me.fireEvent('guaranteedrange', range, start, end);
67787             if (me.cb) {
67788                 me.cb.call(me.scope || me, range);
67789             }
67790         }
67791
67792         me.unmask();
67793     },
67794
67795     // hack to support loadmask
67796     mask: function() {
67797         this.masked = true;
67798         this.fireEvent('beforeload');
67799     },
67800
67801     // hack to support loadmask
67802     unmask: function() {
67803         if (this.masked) {
67804             this.fireEvent('load');
67805         }
67806     },
67807
67808     /**
67809      * Returns the number of pending requests out.
67810      */
67811     hasPendingRequests: function() {
67812         return this.pendingRequests.length;
67813     },
67814
67815
67816     // wait until all requests finish, until guaranteeing the range.
67817     onWaitForGuarantee: function() {
67818         if (!this.hasPendingRequests()) {
67819             this.onGuaranteedRange();
67820         }
67821     },
67822
67823     /**
67824      * Guarantee a specific range, this will load the store with a range (that
67825      * must be the pageSize or smaller) and take care of any loading that may
67826      * be necessary.
67827      */
67828     guaranteeRange: function(start, end, cb, scope) {
67829
67830         end = (end > this.totalCount) ? this.totalCount - 1 : end;
67831
67832         var me = this,
67833             i = start,
67834             prefetchData = me.prefetchData,
67835             range = [],
67836             startLoaded = !!prefetchData.getByKey(start),
67837             endLoaded = !!prefetchData.getByKey(end),
67838             startPage = me.getPageFromRecordIndex(start),
67839             endPage = me.getPageFromRecordIndex(end);
67840
67841         me.cb = cb;
67842         me.scope = scope;
67843
67844         me.requestStart = start;
67845         me.requestEnd = end;
67846         // neither beginning or end are loaded
67847         if (!startLoaded || !endLoaded) {
67848             // same page, lets load it
67849             if (startPage === endPage) {
67850                 me.mask();
67851                 me.prefetchPage(startPage, {
67852                     //blocking: true,
67853                     callback: me.onWaitForGuarantee,
67854                     scope: me
67855                 });
67856             // need to load two pages
67857             } else {
67858                 me.mask();
67859                 me.prefetchPage(startPage, {
67860                     //blocking: true,
67861                     callback: me.onWaitForGuarantee,
67862                     scope: me
67863                 });
67864                 me.prefetchPage(endPage, {
67865                     //blocking: true,
67866                     callback: me.onWaitForGuarantee,
67867                     scope: me
67868                 });
67869             }
67870         // Request was already satisfied via the prefetch
67871         } else {
67872             me.onGuaranteedRange();
67873         }
67874     },
67875
67876     // because prefetchData is stored by index
67877     // this invalidates all of the prefetchedData
67878     sort: function() {
67879         var me = this,
67880             prefetchData = me.prefetchData,
67881             sorters,
67882             start,
67883             end,
67884             range;
67885
67886         if (me.buffered) {
67887             if (me.remoteSort) {
67888                 prefetchData.clear();
67889                 me.callParent(arguments);
67890             } else {
67891                 sorters = me.getSorters();
67892                 start = me.guaranteedStart;
67893                 end = me.guaranteedEnd;
67894
67895                 if (sorters.length) {
67896                     prefetchData.sort(sorters);
67897                     range = prefetchData.getRange();
67898                     prefetchData.clear();
67899                     me.cacheRecords(range);
67900                     delete me.guaranteedStart;
67901                     delete me.guaranteedEnd;
67902                     me.guaranteeRange(start, end);
67903                 }
67904                 me.callParent(arguments);
67905             }
67906         } else {
67907             me.callParent(arguments);
67908         }
67909     },
67910
67911     // overriden to provide striping of the indexes as sorting occurs.
67912     // this cannot be done inside of sort because datachanged has already
67913     // fired and will trigger a repaint of the bound view.
67914     doSort: function(sorterFn) {
67915         var me = this;
67916         if (me.remoteSort) {
67917             //the load function will pick up the new sorters and request the sorted data from the proxy
67918             me.load();
67919         } else {
67920             me.data.sortBy(sorterFn);
67921             if (!me.buffered) {
67922                 var range = me.getRange(),
67923                     ln = range.length,
67924                     i  = 0;
67925                 for (; i < ln; i++) {
67926                     range[i].index = i;
67927                 }
67928             }
67929             me.fireEvent('datachanged', me);
67930         }
67931     },
67932
67933     /**
67934      * Finds the index of the first matching Record in this store by a specific field value.
67935      * @param {String} fieldName The name of the Record field to test.
67936      * @param {String/RegExp} value Either a string that the field value
67937      * should begin with, or a RegExp to test against the field.
67938      * @param {Number} startIndex (optional) The index to start searching at
67939      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67940      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67941      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67942      * @return {Number} The matched index or -1
67943      */
67944     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
67945         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
67946         return fn ? this.data.findIndexBy(fn, null, start) : -1;
67947     },
67948
67949     /**
67950      * Finds the first matching Record in this store by a specific field value.
67951      * @param {String} fieldName The name of the Record field to test.
67952      * @param {String/RegExp} value Either a string that the field value
67953      * should begin with, or a RegExp to test against the field.
67954      * @param {Number} startIndex (optional) The index to start searching at
67955      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67956      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67957      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67958      * @return {Ext.data.Model} The matched record or null
67959      */
67960     findRecord: function() {
67961         var me = this,
67962             index = me.find.apply(me, arguments);
67963         return index !== -1 ? me.getAt(index) : null;
67964     },
67965
67966     /**
67967      * @private
67968      * Returns a filter function used to test a the given property's value. Defers most of the work to
67969      * Ext.util.MixedCollection's createValueMatcher function
67970      * @param {String} property The property to create the filter function for
67971      * @param {String/RegExp} value The string/regex to compare the property value to
67972      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
67973      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
67974      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
67975      * Ignored if anyMatch is true.
67976      */
67977     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
67978         if (Ext.isEmpty(value)) {
67979             return false;
67980         }
67981         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
67982         return function(r) {
67983             return value.test(r.data[property]);
67984         };
67985     },
67986
67987     /**
67988      * Finds the index of the first matching Record in this store by a specific field value.
67989      * @param {String} fieldName The name of the Record field to test.
67990      * @param {Object} value The value to match the field against.
67991      * @param {Number} startIndex (optional) The index to start searching at
67992      * @return {Number} The matched index or -1
67993      */
67994     findExact: function(property, value, start) {
67995         return this.data.findIndexBy(function(rec) {
67996             return rec.get(property) == value;
67997         },
67998         this, start);
67999     },
68000
68001     /**
68002      * Find the index of the first matching Record in this Store by a function.
68003      * If the function returns <tt>true</tt> it is considered a match.
68004      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68005      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68006      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68007      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68008      * </ul>
68009      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68010      * @param {Number} startIndex (optional) The index to start searching at
68011      * @return {Number} The matched index or -1
68012      */
68013     findBy: function(fn, scope, start) {
68014         return this.data.findIndexBy(fn, scope, start);
68015     },
68016
68017     /**
68018      * Collects unique values for a particular dataIndex from this store.
68019      * @param {String} dataIndex The property to collect
68020      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
68021      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
68022      * @return {Object[]} An array of the unique values
68023      **/
68024     collect: function(dataIndex, allowNull, bypassFilter) {
68025         var me = this,
68026             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
68027
68028         return data.collect(dataIndex, 'data', allowNull);
68029     },
68030
68031     /**
68032      * Gets the number of cached records.
68033      * <p>If using paging, this may not be the total size of the dataset. If the data object
68034      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
68035      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
68036      * @return {Number} The number of Records in the Store's cache.
68037      */
68038     getCount: function() {
68039         return this.data.length || 0;
68040     },
68041
68042     /**
68043      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
68044      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
68045      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
68046      * could be loaded into the Store if the Store contained all data
68047      * @return {Number} The total number of Model instances available via the Proxy
68048      */
68049     getTotalCount: function() {
68050         return this.totalCount;
68051     },
68052
68053     /**
68054      * Get the Record at the specified index.
68055      * @param {Number} index The index of the Record to find.
68056      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
68057      */
68058     getAt: function(index) {
68059         return this.data.getAt(index);
68060     },
68061
68062     /**
68063      * Returns a range of Records between specified indices.
68064      * @param {Number} [startIndex=0] The starting index
68065      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
68066      * @return {Ext.data.Model[]} An array of Records
68067      */
68068     getRange: function(start, end) {
68069         return this.data.getRange(start, end);
68070     },
68071
68072     /**
68073      * Get the Record with the specified id.
68074      * @param {String} id The id of the Record to find.
68075      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
68076      */
68077     getById: function(id) {
68078         return (this.snapshot || this.data).findBy(function(record) {
68079             return record.getId() === id;
68080         });
68081     },
68082
68083     /**
68084      * Get the index within the cache of the passed Record.
68085      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68086      * @return {Number} The index of the passed Record. Returns -1 if not found.
68087      */
68088     indexOf: function(record) {
68089         return this.data.indexOf(record);
68090     },
68091
68092
68093     /**
68094      * Get the index within the entire dataset. From 0 to the totalCount.
68095      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68096      * @return {Number} The index of the passed Record. Returns -1 if not found.
68097      */
68098     indexOfTotal: function(record) {
68099         var index = record.index;
68100         if (index || index === 0) {
68101             return index;
68102         }
68103         return this.indexOf(record);
68104     },
68105
68106     /**
68107      * Get the index within the cache of the Record with the passed id.
68108      * @param {String} id The id of the Record to find.
68109      * @return {Number} The index of the Record. Returns -1 if not found.
68110      */
68111     indexOfId: function(id) {
68112         return this.indexOf(this.getById(id));
68113     },
68114
68115     /**
68116      * Remove all items from the store.
68117      * @param {Boolean} silent Prevent the `clear` event from being fired.
68118      */
68119     removeAll: function(silent) {
68120         var me = this;
68121
68122         me.clearData();
68123         if (me.snapshot) {
68124             me.snapshot.clear();
68125         }
68126         if (silent !== true) {
68127             me.fireEvent('clear', me);
68128         }
68129     },
68130
68131     /*
68132      * Aggregation methods
68133      */
68134
68135     /**
68136      * Convenience function for getting the first model instance in the store
68137      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68138      * in the store. The value returned will be an object literal with the key being the group
68139      * name and the first record being the value. The grouped parameter is only honored if
68140      * the store has a groupField.
68141      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
68142      */
68143     first: function(grouped) {
68144         var me = this;
68145
68146         if (grouped && me.isGrouped()) {
68147             return me.aggregate(function(records) {
68148                 return records.length ? records[0] : undefined;
68149             }, me, true);
68150         } else {
68151             return me.data.first();
68152         }
68153     },
68154
68155     /**
68156      * Convenience function for getting the last model instance in the store
68157      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68158      * in the store. The value returned will be an object literal with the key being the group
68159      * name and the last record being the value. The grouped parameter is only honored if
68160      * the store has a groupField.
68161      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
68162      */
68163     last: function(grouped) {
68164         var me = this;
68165
68166         if (grouped && me.isGrouped()) {
68167             return me.aggregate(function(records) {
68168                 var len = records.length;
68169                 return len ? records[len - 1] : undefined;
68170             }, me, true);
68171         } else {
68172             return me.data.last();
68173         }
68174     },
68175
68176     /**
68177      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
68178      * and <tt>end</tt> and returns the result.
68179      * @param {String} field A field in each record
68180      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68181      * in the store. The value returned will be an object literal with the key being the group
68182      * name and the sum for that group being the value. The grouped parameter is only honored if
68183      * the store has a groupField.
68184      * @return {Number} The sum
68185      */
68186     sum: function(field, grouped) {
68187         var me = this;
68188
68189         if (grouped && me.isGrouped()) {
68190             return me.aggregate(me.getSum, me, true, [field]);
68191         } else {
68192             return me.getSum(me.data.items, field);
68193         }
68194     },
68195
68196     // @private, see sum
68197     getSum: function(records, field) {
68198         var total = 0,
68199             i = 0,
68200             len = records.length;
68201
68202         for (; i < len; ++i) {
68203             total += records[i].get(field);
68204         }
68205
68206         return total;
68207     },
68208
68209     /**
68210      * Gets the count of items in the store.
68211      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68212      * in the store. The value returned will be an object literal with the key being the group
68213      * name and the count for each group being the value. The grouped parameter is only honored if
68214      * the store has a groupField.
68215      * @return {Number} the count
68216      */
68217     count: function(grouped) {
68218         var me = this;
68219
68220         if (grouped && me.isGrouped()) {
68221             return me.aggregate(function(records) {
68222                 return records.length;
68223             }, me, true);
68224         } else {
68225             return me.getCount();
68226         }
68227     },
68228
68229     /**
68230      * Gets the minimum value in the store.
68231      * @param {String} field The field in each record
68232      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68233      * in the store. The value returned will be an object literal with the key being the group
68234      * name and the minimum in the group being the value. The grouped parameter is only honored if
68235      * the store has a groupField.
68236      * @return {Object} The minimum value, if no items exist, undefined.
68237      */
68238     min: function(field, grouped) {
68239         var me = this;
68240
68241         if (grouped && me.isGrouped()) {
68242             return me.aggregate(me.getMin, me, true, [field]);
68243         } else {
68244             return me.getMin(me.data.items, field);
68245         }
68246     },
68247
68248     // @private, see min
68249     getMin: function(records, field){
68250         var i = 1,
68251             len = records.length,
68252             value, min;
68253
68254         if (len > 0) {
68255             min = records[0].get(field);
68256         }
68257
68258         for (; i < len; ++i) {
68259             value = records[i].get(field);
68260             if (value < min) {
68261                 min = value;
68262             }
68263         }
68264         return min;
68265     },
68266
68267     /**
68268      * Gets the maximum value in the store.
68269      * @param {String} field The field in each record
68270      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68271      * in the store. The value returned will be an object literal with the key being the group
68272      * name and the maximum in the group being the value. The grouped parameter is only honored if
68273      * the store has a groupField.
68274      * @return {Object} The maximum value, if no items exist, undefined.
68275      */
68276     max: function(field, grouped) {
68277         var me = this;
68278
68279         if (grouped && me.isGrouped()) {
68280             return me.aggregate(me.getMax, me, true, [field]);
68281         } else {
68282             return me.getMax(me.data.items, field);
68283         }
68284     },
68285
68286     // @private, see max
68287     getMax: function(records, field) {
68288         var i = 1,
68289             len = records.length,
68290             value,
68291             max;
68292
68293         if (len > 0) {
68294             max = records[0].get(field);
68295         }
68296
68297         for (; i < len; ++i) {
68298             value = records[i].get(field);
68299             if (value > max) {
68300                 max = value;
68301             }
68302         }
68303         return max;
68304     },
68305
68306     /**
68307      * Gets the average value in the store.
68308      * @param {String} field The field in each record
68309      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68310      * in the store. The value returned will be an object literal with the key being the group
68311      * name and the group average being the value. The grouped parameter is only honored if
68312      * the store has a groupField.
68313      * @return {Object} The average value, if no items exist, 0.
68314      */
68315     average: function(field, grouped) {
68316         var me = this;
68317         if (grouped && me.isGrouped()) {
68318             return me.aggregate(me.getAverage, me, true, [field]);
68319         } else {
68320             return me.getAverage(me.data.items, field);
68321         }
68322     },
68323
68324     // @private, see average
68325     getAverage: function(records, field) {
68326         var i = 0,
68327             len = records.length,
68328             sum = 0;
68329
68330         if (records.length > 0) {
68331             for (; i < len; ++i) {
68332                 sum += records[i].get(field);
68333             }
68334             return sum / len;
68335         }
68336         return 0;
68337     },
68338
68339     /**
68340      * Runs the aggregate function for all the records in the store.
68341      * @param {Function} fn The function to execute. The function is called with a single parameter,
68342      * an array of records for that group.
68343      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
68344      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68345      * in the store. The value returned will be an object literal with the key being the group
68346      * name and the group average being the value. The grouped parameter is only honored if
68347      * the store has a groupField.
68348      * @param {Array} args (optional) Any arguments to append to the function call
68349      * @return {Object} An object literal with the group names and their appropriate values.
68350      */
68351     aggregate: function(fn, scope, grouped, args) {
68352         args = args || [];
68353         if (grouped && this.isGrouped()) {
68354             var groups = this.getGroups(),
68355                 i = 0,
68356                 len = groups.length,
68357                 out = {},
68358                 group;
68359
68360             for (; i < len; ++i) {
68361                 group = groups[i];
68362                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
68363             }
68364             return out;
68365         } else {
68366             return fn.apply(scope || this, [this.data.items].concat(args));
68367         }
68368     }
68369 }, function() {
68370     // A dummy empty store with a fieldless Model defined in it.
68371     // Just for binding to Views which are instantiated with no Store defined.
68372     // They will be able to run and render fine, and be bound to a generated Store later.
68373     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
68374 });
68375
68376 /**
68377  * @author Ed Spencer
68378  * @class Ext.data.JsonStore
68379  * @extends Ext.data.Store
68380  * @ignore
68381  *
68382  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
68383  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
68384  *
68385  * <p>A store configuration would be something like:</p>
68386  *
68387 <pre><code>
68388 var store = new Ext.data.JsonStore({
68389     // store configs
68390     autoDestroy: true,
68391     storeId: 'myStore',
68392
68393     proxy: {
68394         type: 'ajax',
68395         url: 'get-images.php',
68396         reader: {
68397             type: 'json',
68398             root: 'images',
68399             idProperty: 'name'
68400         }
68401     },
68402
68403     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
68404     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
68405 });
68406 </code></pre>
68407  *
68408  * <p>This store is configured to consume a returned object of the form:<pre><code>
68409 {
68410     images: [
68411         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
68412         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
68413     ]
68414 }
68415 </code></pre>
68416  *
68417  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
68418  *
68419  * @xtype jsonstore
68420  */
68421 Ext.define('Ext.data.JsonStore',  {
68422     extend: 'Ext.data.Store',
68423     alias: 'store.json',
68424
68425     /**
68426      * @cfg {Ext.data.DataReader} reader @hide
68427      */
68428     constructor: function(config) {
68429         config = config || {};
68430
68431         Ext.applyIf(config, {
68432             proxy: {
68433                 type  : 'ajax',
68434                 reader: 'json',
68435                 writer: 'json'
68436             }
68437         });
68438
68439         this.callParent([config]);
68440     }
68441 });
68442
68443 /**
68444  * @class Ext.chart.axis.Time
68445  * @extends Ext.chart.axis.Numeric
68446  *
68447  * A type of axis whose units are measured in time values. Use this axis
68448  * for listing dates that you will want to group or dynamically change.
68449  * If you just want to display dates as categories then use the
68450  * Category class for axis instead.
68451  *
68452  * For example:
68453  *
68454  *     axes: [{
68455  *         type: 'Time',
68456  *         position: 'bottom',
68457  *         fields: 'date',
68458  *         title: 'Day',
68459  *         dateFormat: 'M d',
68460  *
68461  *         constrain: true,
68462  *         fromDate: new Date('1/1/11'),
68463  *         toDate: new Date('1/7/11')
68464  *     }]
68465  *
68466  * In this example we're creating a time axis that has as title *Day*.
68467  * The field the axis is bound to is `date`.
68468  * The date format to use to display the text for the axis labels is `M d`
68469  * which is a three letter month abbreviation followed by the day number.
68470  * The time axis will show values for dates between `fromDate` and `toDate`.
68471  * Since `constrain` is set to true all other values for other dates not between
68472  * the fromDate and toDate will not be displayed.
68473  *
68474  */
68475 Ext.define('Ext.chart.axis.Time', {
68476
68477     /* Begin Definitions */
68478
68479     extend: 'Ext.chart.axis.Numeric',
68480
68481     alternateClassName: 'Ext.chart.TimeAxis',
68482
68483     alias: 'axis.time',
68484
68485     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
68486
68487     /* End Definitions */
68488
68489     /**
68490      * @cfg {String/Boolean} dateFormat
68491      * Indicates the format the date will be rendered on.
68492      * For example: 'M d' will render the dates as 'Jan 30', etc.
68493      * For a list of possible format strings see {@link Ext.Date Date}
68494      */
68495     dateFormat: false,
68496
68497     /**
68498      * @cfg {Date} fromDate The starting date for the time axis.
68499      */
68500     fromDate: false,
68501
68502     /**
68503      * @cfg {Date} toDate The ending date for the time axis.
68504      */
68505     toDate: false,
68506
68507     /**
68508      * @cfg {Array/Boolean} step
68509      * An array with two components: The first is the unit of the step (day, month, year, etc).
68510      * The second one is the number of units for the step (1, 2, etc.).
68511      * Defaults to `[Ext.Date.DAY, 1]`.
68512      */
68513     step: [Ext.Date.DAY, 1],
68514     
68515     /**
68516      * @cfg {Boolean} constrain
68517      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
68518      * If false, the time axis will adapt to the new values by adding/removing steps.
68519      */
68520     constrain: false,
68521
68522     // Avoid roundtoDecimal call in Numeric Axis's constructor
68523     roundToDecimal: false,
68524     
68525     constructor: function (config) {
68526         var me = this, label, f, df;
68527         me.callParent([config]);
68528         label = me.label || {};
68529         df = this.dateFormat;
68530         if (df) {
68531             if (label.renderer) {
68532                 f = label.renderer;
68533                 label.renderer = function(v) {
68534                     v = f(v);
68535                     return Ext.Date.format(new Date(f(v)), df);
68536                 };
68537             } else {
68538                 label.renderer = function(v) {
68539                     return Ext.Date.format(new Date(v >> 0), df);
68540                 };
68541             }
68542         }
68543     },
68544
68545     doConstrain: function () {
68546         var me = this,
68547             store = me.chart.store,
68548             data = [],
68549             series = me.chart.series.items,
68550             math = Math,
68551             mmax = math.max,
68552             mmin = math.min,
68553             fields = me.fields,
68554             ln = fields.length,
68555             range = me.getRange(),
68556             min = range.min, max = range.max, i, l, excludes = [],
68557             value, values, rec, data = [];
68558         for (i = 0, l = series.length; i < l; i++) {
68559             excludes[i] = series[i].__excludes;
68560         }
68561         store.each(function(record) {
68562             for (i = 0; i < ln; i++) {
68563                 if (excludes[i]) {
68564                     continue;
68565                 }
68566                 value = record.get(fields[i]);
68567                 if (+value < +min) return;
68568                 if (+value > +max) return;
68569             }
68570             data.push(record);
68571         })
68572         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
68573     },
68574
68575     // Before rendering, set current default step count to be number of records.
68576     processView: function () {
68577         var me = this;
68578         if (me.fromDate) {
68579             me.minimum = +me.fromDate;
68580         }
68581         if (me.toDate) {
68582             me.maximum = +me.toDate;
68583         }
68584         if (me.constrain) {
68585             me.doConstrain();
68586         }
68587      },
68588
68589     // @private modifies the store and creates the labels for the axes.
68590     calcEnds: function() {
68591         var me = this, range, step = me.step;
68592         if (step) {
68593             range = me.getRange();
68594             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
68595             if (me.minimum) {
68596                 range.from = me.minimum;
68597             }
68598             if (me.maximum) {
68599                 range.to = me.maximum;
68600             }
68601             range.step = (range.to - range.from) / range.steps;
68602             return range;
68603         } else {
68604             return me.callParent(arguments);
68605         }
68606     }
68607  });
68608
68609
68610 /**
68611  * @class Ext.chart.series.Series
68612  *
68613  * Series is the abstract class containing the common logic to all chart series. Series includes
68614  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
68615  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
68616  *
68617  * ## Listeners
68618  *
68619  * The series class supports listeners via the Observable syntax. Some of these listeners are:
68620  *
68621  *  - `itemmouseup` When the user interacts with a marker.
68622  *  - `itemmousedown` When the user interacts with a marker.
68623  *  - `itemmousemove` When the user iteracts with a marker.
68624  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
68625  *
68626  * For example:
68627  *
68628  *     series: [{
68629  *             type: 'column',
68630  *             axis: 'left',
68631  *             listeners: {
68632  *                     'afterrender': function() {
68633  *                             console('afterrender');
68634  *                     }
68635  *             },
68636  *             xField: 'category',
68637  *             yField: 'data1'
68638  *     }]
68639  */
68640 Ext.define('Ext.chart.series.Series', {
68641
68642     /* Begin Definitions */
68643
68644     mixins: {
68645         observable: 'Ext.util.Observable',
68646         labels: 'Ext.chart.Label',
68647         highlights: 'Ext.chart.Highlight',
68648         tips: 'Ext.chart.Tip',
68649         callouts: 'Ext.chart.Callout'
68650     },
68651
68652     /* End Definitions */
68653
68654     /**
68655      * @cfg {Boolean/Object} highlight
68656      * If set to `true` it will highlight the markers or the series when hovering
68657      * with the mouse. This parameter can also be an object with the same style
68658      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
68659      * styles to markers and series.
68660      */
68661
68662     /**
68663      * @cfg {Object} tips
68664      * Add tooltips to the visualization's markers. The options for the tips are the
68665      * same configuration used with {@link Ext.tip.ToolTip}. For example:
68666      *
68667      *     tips: {
68668      *       trackMouse: true,
68669      *       width: 140,
68670      *       height: 28,
68671      *       renderer: function(storeItem, item) {
68672      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
68673      *       }
68674      *     },
68675      */
68676
68677     /**
68678      * @cfg {String} type
68679      * The type of series. Set in subclasses.
68680      */
68681     type: null,
68682
68683     /**
68684      * @cfg {String} title
68685      * The human-readable name of the series.
68686      */
68687     title: null,
68688
68689     /**
68690      * @cfg {Boolean} showInLegend
68691      * Whether to show this series in the legend.
68692      */
68693     showInLegend: true,
68694
68695     /**
68696      * @cfg {Function} renderer
68697      * A function that can be overridden to set custom styling properties to each rendered element.
68698      * Passes in (sprite, record, attributes, index, store) to the function.
68699      */
68700     renderer: function(sprite, record, attributes, index, store) {
68701         return attributes;
68702     },
68703
68704     /**
68705      * @cfg {Array} shadowAttributes
68706      * An array with shadow attributes
68707      */
68708     shadowAttributes: null,
68709
68710     //@private triggerdrawlistener flag
68711     triggerAfterDraw: false,
68712
68713     /**
68714      * @cfg {Object} listeners
68715      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
68716      *
68717      *  - itemmouseover
68718      *  - itemmouseout
68719      *  - itemmousedown
68720      *  - itemmouseup
68721      */
68722
68723     constructor: function(config) {
68724         var me = this;
68725         if (config) {
68726             Ext.apply(me, config);
68727         }
68728
68729         me.shadowGroups = [];
68730
68731         me.mixins.labels.constructor.call(me, config);
68732         me.mixins.highlights.constructor.call(me, config);
68733         me.mixins.tips.constructor.call(me, config);
68734         me.mixins.callouts.constructor.call(me, config);
68735
68736         me.addEvents({
68737             scope: me,
68738             itemmouseover: true,
68739             itemmouseout: true,
68740             itemmousedown: true,
68741             itemmouseup: true,
68742             mouseleave: true,
68743             afterdraw: true,
68744
68745             /**
68746              * @event titlechange
68747              * Fires when the series title is changed via {@link #setTitle}.
68748              * @param {String} title The new title value
68749              * @param {Number} index The index in the collection of titles
68750              */
68751             titlechange: true
68752         });
68753
68754         me.mixins.observable.constructor.call(me, config);
68755
68756         me.on({
68757             scope: me,
68758             itemmouseover: me.onItemMouseOver,
68759             itemmouseout: me.onItemMouseOut,
68760             mouseleave: me.onMouseLeave
68761         });
68762     },
68763     
68764     /**
68765      * Iterate over each of the records for this series. The default implementation simply iterates
68766      * through the entire data store, but individual series implementations can override this to
68767      * provide custom handling, e.g. adding/removing records.
68768      * @param {Function} fn The function to execute for each record.
68769      * @param {Object} scope Scope for the fn.
68770      */
68771     eachRecord: function(fn, scope) {
68772         var chart = this.chart;
68773         (chart.substore || chart.store).each(fn, scope);
68774     },
68775
68776     /**
68777      * Return the number of records being displayed in this series. Defaults to the number of
68778      * records in the store; individual series implementations can override to provide custom handling.
68779      */
68780     getRecordCount: function() {
68781         var chart = this.chart,
68782             store = chart.substore || chart.store;
68783         return store ? store.getCount() : 0;
68784     },
68785
68786     /**
68787      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
68788      * @param index
68789      */
68790     isExcluded: function(index) {
68791         var excludes = this.__excludes;
68792         return !!(excludes && excludes[index]);
68793     },
68794
68795     // @private set the bbox and clipBox for the series
68796     setBBox: function(noGutter) {
68797         var me = this,
68798             chart = me.chart,
68799             chartBBox = chart.chartBBox,
68800             gutterX = noGutter ? 0 : chart.maxGutter[0],
68801             gutterY = noGutter ? 0 : chart.maxGutter[1],
68802             clipBox, bbox;
68803
68804         clipBox = {
68805             x: chartBBox.x,
68806             y: chartBBox.y,
68807             width: chartBBox.width,
68808             height: chartBBox.height
68809         };
68810         me.clipBox = clipBox;
68811
68812         bbox = {
68813             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
68814             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
68815             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
68816             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
68817         };
68818         me.bbox = bbox;
68819     },
68820
68821     // @private set the animation for the sprite
68822     onAnimate: function(sprite, attr) {
68823         var me = this;
68824         sprite.stopAnimation();
68825         if (me.triggerAfterDraw) {
68826             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
68827         } else {
68828             me.triggerAfterDraw = true;
68829             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
68830                 listeners: {
68831                     'afteranimate': function() {
68832                         me.triggerAfterDraw = false;
68833                         me.fireEvent('afterrender');
68834                     }
68835                 }
68836             }));
68837         }
68838     },
68839
68840     // @private return the gutter.
68841     getGutters: function() {
68842         return [0, 0];
68843     },
68844
68845     // @private wrapper for the itemmouseover event.
68846     onItemMouseOver: function(item) {
68847         var me = this;
68848         if (item.series === me) {
68849             if (me.highlight) {
68850                 me.highlightItem(item);
68851             }
68852             if (me.tooltip) {
68853                 me.showTip(item);
68854             }
68855         }
68856     },
68857
68858     // @private wrapper for the itemmouseout event.
68859     onItemMouseOut: function(item) {
68860         var me = this;
68861         if (item.series === me) {
68862             me.unHighlightItem();
68863             if (me.tooltip) {
68864                 me.hideTip(item);
68865             }
68866         }
68867     },
68868
68869     // @private wrapper for the mouseleave event.
68870     onMouseLeave: function() {
68871         var me = this;
68872         me.unHighlightItem();
68873         if (me.tooltip) {
68874             me.hideTip();
68875         }
68876     },
68877
68878     /**
68879      * For a given x/y point relative to the Surface, find a corresponding item from this
68880      * series, if any.
68881      * @param {Number} x
68882      * @param {Number} y
68883      * @return {Object} An object describing the item, or null if there is no matching item.
68884      * The exact contents of this object will vary by series type, but should always contain the following:
68885      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
68886      * @return {Object} return.value the value(s) of the item's data point
68887      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
68888      * for this data item, which can be used as e.g. a tooltip anchor point.
68889      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
68890      */
68891     getItemForPoint: function(x, y) {
68892         //if there are no items to query just return null.
68893         if (!this.items || !this.items.length || this.seriesIsHidden) {
68894             return null;
68895         }
68896         var me = this,
68897             items = me.items,
68898             bbox = me.bbox,
68899             item, i, ln;
68900         // Check bounds
68901         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
68902             return null;
68903         }
68904         for (i = 0, ln = items.length; i < ln; i++) {
68905             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
68906                 return items[i];
68907             }
68908         }
68909
68910         return null;
68911     },
68912
68913     isItemInPoint: function(x, y, item, i) {
68914         return false;
68915     },
68916
68917     /**
68918      * Hides all the elements in the series.
68919      */
68920     hideAll: function() {
68921         var me = this,
68922             items = me.items,
68923             item, len, i, j, l, sprite, shadows;
68924
68925         me.seriesIsHidden = true;
68926         me._prevShowMarkers = me.showMarkers;
68927
68928         me.showMarkers = false;
68929         //hide all labels
68930         me.hideLabels(0);
68931         //hide all sprites
68932         for (i = 0, len = items.length; i < len; i++) {
68933             item = items[i];
68934             sprite = item.sprite;
68935             if (sprite) {
68936                 sprite.setAttributes({
68937                     hidden: true
68938                 }, true);
68939             }
68940
68941             if (sprite && sprite.shadows) {
68942                 shadows = sprite.shadows;
68943                 for (j = 0, l = shadows.length; j < l; ++j) {
68944                     shadows[j].setAttributes({
68945                         hidden: true
68946                     }, true);
68947                 }
68948             }
68949         }
68950     },
68951
68952     /**
68953      * Shows all the elements in the series.
68954      */
68955     showAll: function() {
68956         var me = this,
68957             prevAnimate = me.chart.animate;
68958         me.chart.animate = false;
68959         me.seriesIsHidden = false;
68960         me.showMarkers = me._prevShowMarkers;
68961         me.drawSeries();
68962         me.chart.animate = prevAnimate;
68963     },
68964
68965     /**
68966      * Returns a string with the color to be used for the series legend item.
68967      */
68968     getLegendColor: function(index) {
68969         var me = this, fill, stroke;
68970         if (me.seriesStyle) {
68971             fill = me.seriesStyle.fill;
68972             stroke = me.seriesStyle.stroke;
68973             if (fill && fill != 'none') {
68974                 return fill;
68975             }
68976             return stroke;
68977         }
68978         return '#000';
68979     },
68980
68981     /**
68982      * Checks whether the data field should be visible in the legend
68983      * @private
68984      * @param {Number} index The index of the current item
68985      */
68986     visibleInLegend: function(index){
68987         var excludes = this.__excludes;
68988         if (excludes) {
68989             return !excludes[index];
68990         }
68991         return !this.seriesIsHidden;
68992     },
68993
68994     /**
68995      * Changes the value of the {@link #title} for the series.
68996      * Arguments can take two forms:
68997      * <ul>
68998      * <li>A single String value: this will be used as the new single title for the series (applies
68999      * to series with only one yField)</li>
69000      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
69001      * </ul>
69002      * @param {Number} index
69003      * @param {String} title
69004      */
69005     setTitle: function(index, title) {
69006         var me = this,
69007             oldTitle = me.title;
69008
69009         if (Ext.isString(index)) {
69010             title = index;
69011             index = 0;
69012         }
69013
69014         if (Ext.isArray(oldTitle)) {
69015             oldTitle[index] = title;
69016         } else {
69017             me.title = title;
69018         }
69019
69020         me.fireEvent('titlechange', title, index);
69021     }
69022 });
69023
69024 /**
69025  * @class Ext.chart.series.Cartesian
69026  * @extends Ext.chart.series.Series
69027  *
69028  * Common base class for series implementations which plot values using x/y coordinates.
69029  */
69030 Ext.define('Ext.chart.series.Cartesian', {
69031
69032     /* Begin Definitions */
69033
69034     extend: 'Ext.chart.series.Series',
69035
69036     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
69037
69038     /* End Definitions */
69039
69040     /**
69041      * The field used to access the x axis value from the items from the data
69042      * source.
69043      *
69044      * @cfg xField
69045      * @type String
69046      */
69047     xField: null,
69048
69049     /**
69050      * The field used to access the y-axis value from the items from the data
69051      * source.
69052      *
69053      * @cfg yField
69054      * @type String
69055      */
69056     yField: null,
69057
69058     /**
69059      * @cfg {String} axis
69060      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
69061      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
69062      * relative scale will be used.
69063      */
69064     axis: 'left',
69065
69066     getLegendLabels: function() {
69067         var me = this,
69068             labels = [],
69069             combinations = me.combinations;
69070
69071         Ext.each([].concat(me.yField), function(yField, i) {
69072             var title = me.title;
69073             // Use the 'title' config if present, otherwise use the raw yField name
69074             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
69075         });
69076
69077         // Handle yFields combined via legend drag-drop
69078         if (combinations) {
69079             Ext.each(combinations, function(combo) {
69080                 var label0 = labels[combo[0]],
69081                     label1 = labels[combo[1]];
69082                 labels[combo[1]] = label0 + ' & ' + label1;
69083                 labels.splice(combo[0], 1);
69084             });
69085         }
69086
69087         return labels;
69088     },
69089
69090     /**
69091      * @protected Iterates over a given record's values for each of this series's yFields,
69092      * executing a given function for each value. Any yFields that have been combined
69093      * via legend drag-drop will be treated as a single value.
69094      * @param {Ext.data.Model} record
69095      * @param {Function} fn
69096      * @param {Object} scope
69097      */
69098     eachYValue: function(record, fn, scope) {
69099         Ext.each(this.getYValueAccessors(), function(accessor, i) {
69100             fn.call(scope, accessor(record), i);
69101         });
69102     },
69103
69104     /**
69105      * @protected Returns the number of yField values, taking into account fields combined
69106      * via legend drag-drop.
69107      * @return {Number}
69108      */
69109     getYValueCount: function() {
69110         return this.getYValueAccessors().length;
69111     },
69112
69113     combine: function(index1, index2) {
69114         var me = this,
69115             accessors = me.getYValueAccessors(),
69116             accessor1 = accessors[index1],
69117             accessor2 = accessors[index2];
69118
69119         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
69120         accessors[index2] = function(record) {
69121             return accessor1(record) + accessor2(record);
69122         };
69123         accessors.splice(index1, 1);
69124
69125         me.callParent([index1, index2]);
69126     },
69127
69128     clearCombinations: function() {
69129         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
69130         delete this.yValueAccessors;
69131         this.callParent();
69132     },
69133
69134     /**
69135      * @protected Returns an array of functions, each of which returns the value of the yField
69136      * corresponding to function's index in the array, for a given record (each function takes the
69137      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
69138      * this list of accessors will be kept in sync with those combinations.
69139      * @return {Array} array of accessor functions
69140      */
69141     getYValueAccessors: function() {
69142         var me = this,
69143             accessors = me.yValueAccessors;
69144         if (!accessors) {
69145             accessors = me.yValueAccessors = [];
69146             Ext.each([].concat(me.yField), function(yField) {
69147                 accessors.push(function(record) {
69148                     return record.get(yField);
69149                 });
69150             });
69151         }
69152         return accessors;
69153     },
69154
69155     /**
69156      * Calculate the min and max values for this series's xField.
69157      * @return {Array} [min, max]
69158      */
69159     getMinMaxXValues: function() {
69160         var me = this,
69161             min, max,
69162             xField = me.xField;
69163
69164         if (me.getRecordCount() > 0) {
69165             min = Infinity;
69166             max = -min;
69167             me.eachRecord(function(record) {
69168                 var xValue = record.get(xField);
69169                 if (xValue > max) {
69170                     max = xValue;
69171                 }
69172                 if (xValue < min) {
69173                     min = xValue;
69174                 }
69175             });
69176         } else {
69177             min = max = 0;
69178         }
69179         return [min, max];
69180     },
69181
69182     /**
69183      * Calculate the min and max values for this series's yField(s). Takes into account yField
69184      * combinations, exclusions, and stacking.
69185      * @return {Array} [min, max]
69186      */
69187     getMinMaxYValues: function() {
69188         var me = this,
69189             stacked = me.stacked,
69190             min, max,
69191             positiveTotal, negativeTotal;
69192
69193         function eachYValueStacked(yValue, i) {
69194             if (!me.isExcluded(i)) {
69195                 if (yValue < 0) {
69196                     negativeTotal += yValue;
69197                 } else {
69198                     positiveTotal += yValue;
69199                 }
69200             }
69201         }
69202
69203         function eachYValue(yValue, i) {
69204             if (!me.isExcluded(i)) {
69205                 if (yValue > max) {
69206                     max = yValue;
69207                 }
69208                 if (yValue < min) {
69209                     min = yValue;
69210                 }
69211             }
69212         }
69213
69214         if (me.getRecordCount() > 0) {
69215             min = Infinity;
69216             max = -min;
69217             me.eachRecord(function(record) {
69218                 if (stacked) {
69219                     positiveTotal = 0;
69220                     negativeTotal = 0;
69221                     me.eachYValue(record, eachYValueStacked);
69222                     if (positiveTotal > max) {
69223                         max = positiveTotal;
69224                     }
69225                     if (negativeTotal < min) {
69226                         min = negativeTotal;
69227                     }
69228                 } else {
69229                     me.eachYValue(record, eachYValue);
69230                 }
69231             });
69232         } else {
69233             min = max = 0;
69234         }
69235         return [min, max];
69236     },
69237
69238     getAxesForXAndYFields: function() {
69239         var me = this,
69240             axes = me.chart.axes,
69241             axis = [].concat(me.axis),
69242             xAxis, yAxis;
69243
69244         if (Ext.Array.indexOf(axis, 'top') > -1) {
69245             xAxis = 'top';
69246         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
69247             xAxis = 'bottom';
69248         } else {
69249             if (axes.get('top')) {
69250                 xAxis = 'top';
69251             } else if (axes.get('bottom')) {
69252                 xAxis = 'bottom';
69253             }
69254         }
69255
69256         if (Ext.Array.indexOf(axis, 'left') > -1) {
69257             yAxis = 'left';
69258         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
69259             yAxis = 'right';
69260         } else {
69261             if (axes.get('left')) {
69262                 yAxis = 'left';
69263             } else if (axes.get('right')) {
69264                 yAxis = 'right';
69265             }
69266         }
69267
69268         return {
69269             xAxis: xAxis,
69270             yAxis: yAxis
69271         };
69272     }
69273
69274
69275 });
69276
69277 /**
69278  * @class Ext.chart.series.Area
69279  * @extends Ext.chart.series.Cartesian
69280  *
69281  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
69282  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
69283  * documentation for more information. A typical configuration object for the area series could be:
69284  *
69285  *     @example
69286  *     var store = Ext.create('Ext.data.JsonStore', {
69287  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69288  *         data: [
69289  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
69290  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
69291  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
69292  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
69293  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
69294  *         ]
69295  *     });
69296  *
69297  *     Ext.create('Ext.chart.Chart', {
69298  *         renderTo: Ext.getBody(),
69299  *         width: 500,
69300  *         height: 300,
69301  *         store: store,
69302  *         axes: [
69303  *             {
69304  *                 type: 'Numeric',
69305  *                 grid: true,
69306  *                 position: 'left',
69307  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
69308  *                 title: 'Sample Values',
69309  *                 grid: {
69310  *                     odd: {
69311  *                         opacity: 1,
69312  *                         fill: '#ddd',
69313  *                         stroke: '#bbb',
69314  *                         'stroke-width': 1
69315  *                     }
69316  *                 },
69317  *                 minimum: 0,
69318  *                 adjustMinimumByMajorUnit: 0
69319  *             },
69320  *             {
69321  *                 type: 'Category',
69322  *                 position: 'bottom',
69323  *                 fields: ['name'],
69324  *                 title: 'Sample Metrics',
69325  *                 grid: true,
69326  *                 label: {
69327  *                     rotate: {
69328  *                         degrees: 315
69329  *                     }
69330  *                 }
69331  *             }
69332  *         ],
69333  *         series: [{
69334  *             type: 'area',
69335  *             highlight: false,
69336  *             axis: 'left',
69337  *             xField: 'name',
69338  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
69339  *             style: {
69340  *                 opacity: 0.93
69341  *             }
69342  *         }]
69343  *     });
69344  *
69345  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
69346  * 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,
69347  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
69348  * to the style object.
69349  *
69350  * @xtype area
69351  */
69352 Ext.define('Ext.chart.series.Area', {
69353
69354     /* Begin Definitions */
69355
69356     extend: 'Ext.chart.series.Cartesian',
69357
69358     alias: 'series.area',
69359
69360     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
69361
69362     /* End Definitions */
69363
69364     type: 'area',
69365
69366     // @private Area charts are alyways stacked
69367     stacked: true,
69368
69369     /**
69370      * @cfg {Object} style
69371      * Append styling properties to this object for it to override theme properties.
69372      */
69373     style: {},
69374
69375     constructor: function(config) {
69376         this.callParent(arguments);
69377         var me = this,
69378             surface = me.chart.surface,
69379             i, l;
69380         Ext.apply(me, config, {
69381             __excludes: [],
69382             highlightCfg: {
69383                 lineWidth: 3,
69384                 stroke: '#55c',
69385                 opacity: 0.8,
69386                 color: '#f00'
69387             }
69388         });
69389         if (me.highlight) {
69390             me.highlightSprite = surface.add({
69391                 type: 'path',
69392                 path: ['M', 0, 0],
69393                 zIndex: 1000,
69394                 opacity: 0.3,
69395                 lineWidth: 5,
69396                 hidden: true,
69397                 stroke: '#444'
69398             });
69399         }
69400         me.group = surface.getGroup(me.seriesId);
69401     },
69402
69403     // @private Shrinks dataSets down to a smaller size
69404     shrink: function(xValues, yValues, size) {
69405         var len = xValues.length,
69406             ratio = Math.floor(len / size),
69407             i, j,
69408             xSum = 0,
69409             yCompLen = this.areas.length,
69410             ySum = [],
69411             xRes = [],
69412             yRes = [];
69413         //initialize array
69414         for (j = 0; j < yCompLen; ++j) {
69415             ySum[j] = 0;
69416         }
69417         for (i = 0; i < len; ++i) {
69418             xSum += xValues[i];
69419             for (j = 0; j < yCompLen; ++j) {
69420                 ySum[j] += yValues[i][j];
69421             }
69422             if (i % ratio == 0) {
69423                 //push averages
69424                 xRes.push(xSum/ratio);
69425                 for (j = 0; j < yCompLen; ++j) {
69426                     ySum[j] /= ratio;
69427                 }
69428                 yRes.push(ySum);
69429                 //reset sum accumulators
69430                 xSum = 0;
69431                 for (j = 0, ySum = []; j < yCompLen; ++j) {
69432                     ySum[j] = 0;
69433                 }
69434             }
69435         }
69436         return {
69437             x: xRes,
69438             y: yRes
69439         };
69440     },
69441
69442     // @private Get chart and data boundaries
69443     getBounds: function() {
69444         var me = this,
69445             chart = me.chart,
69446             store = chart.getChartStore(),
69447             areas = [].concat(me.yField),
69448             areasLen = areas.length,
69449             xValues = [],
69450             yValues = [],
69451             infinity = Infinity,
69452             minX = infinity,
69453             minY = infinity,
69454             maxX = -infinity,
69455             maxY = -infinity,
69456             math = Math,
69457             mmin = math.min,
69458             mmax = math.max,
69459             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
69460
69461         me.setBBox();
69462         bbox = me.bbox;
69463
69464         // Run through the axis
69465         if (me.axis) {
69466             axis = chart.axes.get(me.axis);
69467             if (axis) {
69468                 out = axis.calcEnds();
69469                 minY = out.from || axis.prevMin;
69470                 maxY = mmax(out.to || axis.prevMax, 0);
69471             }
69472         }
69473
69474         if (me.yField && !Ext.isNumber(minY)) {
69475             axis = Ext.create('Ext.chart.axis.Axis', {
69476                 chart: chart,
69477                 fields: [].concat(me.yField)
69478             });
69479             out = axis.calcEnds();
69480             minY = out.from || axis.prevMin;
69481             maxY = mmax(out.to || axis.prevMax, 0);
69482         }
69483
69484         if (!Ext.isNumber(minY)) {
69485             minY = 0;
69486         }
69487         if (!Ext.isNumber(maxY)) {
69488             maxY = 0;
69489         }
69490
69491         store.each(function(record, i) {
69492             xValue = record.get(me.xField);
69493             yValue = [];
69494             if (typeof xValue != 'number') {
69495                 xValue = i;
69496             }
69497             xValues.push(xValue);
69498             acumY = 0;
69499             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
69500                 areaElem = record.get(areas[areaIndex]);
69501                 if (typeof areaElem == 'number') {
69502                     minY = mmin(minY, areaElem);
69503                     yValue.push(areaElem);
69504                     acumY += areaElem;
69505                 }
69506             }
69507             minX = mmin(minX, xValue);
69508             maxX = mmax(maxX, xValue);
69509             maxY = mmax(maxY, acumY);
69510             yValues.push(yValue);
69511         }, me);
69512
69513         xScale = bbox.width / ((maxX - minX) || 1);
69514         yScale = bbox.height / ((maxY - minY) || 1);
69515
69516         ln = xValues.length;
69517         if ((ln > bbox.width) && me.areas) {
69518             sumValues = me.shrink(xValues, yValues, bbox.width);
69519             xValues = sumValues.x;
69520             yValues = sumValues.y;
69521         }
69522
69523         return {
69524             bbox: bbox,
69525             minX: minX,
69526             minY: minY,
69527             xValues: xValues,
69528             yValues: yValues,
69529             xScale: xScale,
69530             yScale: yScale,
69531             areasLen: areasLen
69532         };
69533     },
69534
69535     // @private Build an array of paths for the chart
69536     getPaths: function() {
69537         var me = this,
69538             chart = me.chart,
69539             store = chart.getChartStore(),
69540             first = true,
69541             bounds = me.getBounds(),
69542             bbox = bounds.bbox,
69543             items = me.items = [],
69544             componentPaths = [],
69545             componentPath,
69546             paths = [],
69547             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
69548
69549         ln = bounds.xValues.length;
69550         // Start the path
69551         for (i = 0; i < ln; i++) {
69552             xValue = bounds.xValues[i];
69553             yValue = bounds.yValues[i];
69554             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
69555             acumY = 0;
69556             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69557                 // Excluded series
69558                 if (me.__excludes[areaIndex]) {
69559                     continue;
69560                 }
69561                 if (!componentPaths[areaIndex]) {
69562                     componentPaths[areaIndex] = [];
69563                 }
69564                 areaElem = yValue[areaIndex];
69565                 acumY += areaElem;
69566                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
69567                 if (!paths[areaIndex]) {
69568                     paths[areaIndex] = ['M', x, y];
69569                     componentPaths[areaIndex].push(['L', x, y]);
69570                 } else {
69571                     paths[areaIndex].push('L', x, y);
69572                     componentPaths[areaIndex].push(['L', x, y]);
69573                 }
69574                 if (!items[areaIndex]) {
69575                     items[areaIndex] = {
69576                         pointsUp: [],
69577                         pointsDown: [],
69578                         series: me
69579                     };
69580                 }
69581                 items[areaIndex].pointsUp.push([x, y]);
69582             }
69583         }
69584
69585         // Close the paths
69586         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69587             // Excluded series
69588             if (me.__excludes[areaIndex]) {
69589                 continue;
69590             }
69591             path = paths[areaIndex];
69592             // Close bottom path to the axis
69593             if (areaIndex == 0 || first) {
69594                 first = false;
69595                 path.push('L', x, bbox.y + bbox.height,
69596                           'L', bbox.x, bbox.y + bbox.height,
69597                           'Z');
69598             }
69599             // Close other paths to the one before them
69600             else {
69601                 componentPath = componentPaths[prevAreaIndex];
69602                 componentPath.reverse();
69603                 path.push('L', x, componentPath[0][2]);
69604                 for (i = 0; i < ln; i++) {
69605                     path.push(componentPath[i][0],
69606                               componentPath[i][1],
69607                               componentPath[i][2]);
69608                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
69609                 }
69610                 path.push('L', bbox.x, path[2], 'Z');
69611             }
69612             prevAreaIndex = areaIndex;
69613         }
69614         return {
69615             paths: paths,
69616             areasLen: bounds.areasLen
69617         };
69618     },
69619
69620     /**
69621      * Draws the series for the current chart.
69622      */
69623     drawSeries: function() {
69624         var me = this,
69625             chart = me.chart,
69626             store = chart.getChartStore(),
69627             surface = chart.surface,
69628             animate = chart.animate,
69629             group = me.group,
69630             endLineStyle = Ext.apply(me.seriesStyle, me.style),
69631             colorArrayStyle = me.colorArrayStyle,
69632             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
69633             areaIndex, areaElem, paths, path, rendererAttributes;
69634
69635         me.unHighlightItem();
69636         me.cleanHighlights();
69637
69638         if (!store || !store.getCount()) {
69639             return;
69640         }
69641
69642         paths = me.getPaths();
69643
69644         if (!me.areas) {
69645             me.areas = [];
69646         }
69647
69648         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
69649             // Excluded series
69650             if (me.__excludes[areaIndex]) {
69651                 continue;
69652             }
69653             if (!me.areas[areaIndex]) {
69654                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
69655                     type: 'path',
69656                     group: group,
69657                     // 'clip-rect': me.clipBox,
69658                     path: paths.paths[areaIndex],
69659                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
69660                     fill: colorArrayStyle[areaIndex % colorArrayLength]
69661                 }, endLineStyle || {}));
69662             }
69663             areaElem = me.areas[areaIndex];
69664             path = paths.paths[areaIndex];
69665             if (animate) {
69666                 //Add renderer to line. There is not a unique record associated with this.
69667                 rendererAttributes = me.renderer(areaElem, false, {
69668                     path: path,
69669                     // 'clip-rect': me.clipBox,
69670                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69671                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69672                 }, areaIndex, store);
69673                 //fill should not be used here but when drawing the special fill path object
69674                 me.animation = me.onAnimate(areaElem, {
69675                     to: rendererAttributes
69676                 });
69677             } else {
69678                 rendererAttributes = me.renderer(areaElem, false, {
69679                     path: path,
69680                     // 'clip-rect': me.clipBox,
69681                     hidden: false,
69682                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69683                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69684                 }, areaIndex, store);
69685                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
69686             }
69687         }
69688         me.renderLabels();
69689         me.renderCallouts();
69690     },
69691
69692     // @private
69693     onAnimate: function(sprite, attr) {
69694         sprite.show();
69695         return this.callParent(arguments);
69696     },
69697
69698     // @private
69699     onCreateLabel: function(storeItem, item, i, display) {
69700         var me = this,
69701             group = me.labelsGroup,
69702             config = me.label,
69703             bbox = me.bbox,
69704             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
69705
69706         return me.chart.surface.add(Ext.apply({
69707             'type': 'text',
69708             'text-anchor': 'middle',
69709             'group': group,
69710             'x': item.point[0],
69711             'y': bbox.y + bbox.height / 2
69712         }, endLabelStyle || {}));
69713     },
69714
69715     // @private
69716     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
69717         var me = this,
69718             chart = me.chart,
69719             resizing = chart.resizing,
69720             config = me.label,
69721             format = config.renderer,
69722             field = config.field,
69723             bbox = me.bbox,
69724             x = item.point[0],
69725             y = item.point[1],
69726             bb, width, height;
69727
69728         label.setAttributes({
69729             text: format(storeItem.get(field[index])),
69730             hidden: true
69731         }, true);
69732
69733         bb = label.getBBox();
69734         width = bb.width / 2;
69735         height = bb.height / 2;
69736
69737         x = x - width < bbox.x? bbox.x + width : x;
69738         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
69739         y = y - height < bbox.y? bbox.y + height : y;
69740         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
69741
69742         if (me.chart.animate && !me.chart.resizing) {
69743             label.show(true);
69744             me.onAnimate(label, {
69745                 to: {
69746                     x: x,
69747                     y: y
69748                 }
69749             });
69750         } else {
69751             label.setAttributes({
69752                 x: x,
69753                 y: y
69754             }, true);
69755             if (resizing) {
69756                 me.animation.on('afteranimate', function() {
69757                     label.show(true);
69758                 });
69759             } else {
69760                 label.show(true);
69761             }
69762         }
69763     },
69764
69765     // @private
69766     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
69767         var me = this,
69768             chart = me.chart,
69769             surface = chart.surface,
69770             resizing = chart.resizing,
69771             config = me.callouts,
69772             items = me.items,
69773             prev = (i == 0) ? false : items[i -1].point,
69774             next = (i == items.length -1) ? false : items[i +1].point,
69775             cur = item.point,
69776             dir, norm, normal, a, aprev, anext,
69777             bbox = callout.label.getBBox(),
69778             offsetFromViz = 30,
69779             offsetToSide = 10,
69780             offsetBox = 3,
69781             boxx, boxy, boxw, boxh,
69782             p, clipRect = me.clipRect,
69783             x, y;
69784
69785         //get the right two points
69786         if (!prev) {
69787             prev = cur;
69788         }
69789         if (!next) {
69790             next = cur;
69791         }
69792         a = (next[1] - prev[1]) / (next[0] - prev[0]);
69793         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
69794         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
69795
69796         norm = Math.sqrt(1 + a * a);
69797         dir = [1 / norm, a / norm];
69798         normal = [-dir[1], dir[0]];
69799
69800         //keep the label always on the outer part of the "elbow"
69801         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
69802             normal[0] *= -1;
69803             normal[1] *= -1;
69804         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
69805             normal[0] *= -1;
69806             normal[1] *= -1;
69807         }
69808
69809         //position
69810         x = cur[0] + normal[0] * offsetFromViz;
69811         y = cur[1] + normal[1] * offsetFromViz;
69812
69813         //box position and dimensions
69814         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69815         boxy = y - bbox.height /2 - offsetBox;
69816         boxw = bbox.width + 2 * offsetBox;
69817         boxh = bbox.height + 2 * offsetBox;
69818
69819         //now check if we're out of bounds and invert the normal vector correspondingly
69820         //this may add new overlaps between labels (but labels won't be out of bounds).
69821         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
69822             normal[0] *= -1;
69823         }
69824         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
69825             normal[1] *= -1;
69826         }
69827
69828         //update positions
69829         x = cur[0] + normal[0] * offsetFromViz;
69830         y = cur[1] + normal[1] * offsetFromViz;
69831
69832         //update box position and dimensions
69833         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69834         boxy = y - bbox.height /2 - offsetBox;
69835         boxw = bbox.width + 2 * offsetBox;
69836         boxh = bbox.height + 2 * offsetBox;
69837
69838         //set the line from the middle of the pie to the box.
69839         callout.lines.setAttributes({
69840             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
69841         }, true);
69842         //set box position
69843         callout.box.setAttributes({
69844             x: boxx,
69845             y: boxy,
69846             width: boxw,
69847             height: boxh
69848         }, true);
69849         //set text position
69850         callout.label.setAttributes({
69851             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
69852             y: y
69853         }, true);
69854         for (p in callout) {
69855             callout[p].show(true);
69856         }
69857     },
69858
69859     isItemInPoint: function(x, y, item, i) {
69860         var me = this,
69861             pointsUp = item.pointsUp,
69862             pointsDown = item.pointsDown,
69863             abs = Math.abs,
69864             dist = Infinity, p, pln, point;
69865
69866         for (p = 0, pln = pointsUp.length; p < pln; p++) {
69867             point = [pointsUp[p][0], pointsUp[p][1]];
69868             if (dist > abs(x - point[0])) {
69869                 dist = abs(x - point[0]);
69870             } else {
69871                 point = pointsUp[p -1];
69872                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
69873                     item.storeIndex = p -1;
69874                     item.storeField = me.yField[i];
69875                     item.storeItem = me.chart.store.getAt(p -1);
69876                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
69877                     return true;
69878                 } else {
69879                     break;
69880                 }
69881             }
69882         }
69883         return false;
69884     },
69885
69886     /**
69887      * Highlight this entire series.
69888      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69889      */
69890     highlightSeries: function() {
69891         var area, to, fillColor;
69892         if (this._index !== undefined) {
69893             area = this.areas[this._index];
69894             if (area.__highlightAnim) {
69895                 area.__highlightAnim.paused = true;
69896             }
69897             area.__highlighted = true;
69898             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
69899             area.__prevFill = area.__prevFill || area.attr.fill;
69900             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
69901             fillColor = Ext.draw.Color.fromString(area.__prevFill);
69902             to = {
69903                 lineWidth: (area.__prevLineWidth || 0) + 2
69904             };
69905             if (fillColor) {
69906                 to.fill = fillColor.getLighter(0.2).toString();
69907             }
69908             else {
69909                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
69910             }
69911             if (this.chart.animate) {
69912                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
69913                     target: area,
69914                     to: to
69915                 }, this.chart.animate));
69916             }
69917             else {
69918                 area.setAttributes(to, true);
69919             }
69920         }
69921     },
69922
69923     /**
69924      * UnHighlight this entire series.
69925      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69926      */
69927     unHighlightSeries: function() {
69928         var area;
69929         if (this._index !== undefined) {
69930             area = this.areas[this._index];
69931             if (area.__highlightAnim) {
69932                 area.__highlightAnim.paused = true;
69933             }
69934             if (area.__highlighted) {
69935                 area.__highlighted = false;
69936                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
69937                     target: area,
69938                     to: {
69939                         fill: area.__prevFill,
69940                         opacity: area.__prevOpacity,
69941                         lineWidth: area.__prevLineWidth
69942                     }
69943                 });
69944             }
69945         }
69946     },
69947
69948     /**
69949      * Highlight the specified item. If no item is provided the whole series will be highlighted.
69950      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69951      */
69952     highlightItem: function(item) {
69953         var me = this,
69954             points, path;
69955         if (!item) {
69956             this.highlightSeries();
69957             return;
69958         }
69959         points = item._points;
69960         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
69961                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
69962         me.highlightSprite.setAttributes({
69963             path: path,
69964             hidden: false
69965         }, true);
69966     },
69967
69968     /**
69969      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
69970      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
69971      */
69972     unHighlightItem: function(item) {
69973         if (!item) {
69974             this.unHighlightSeries();
69975         }
69976
69977         if (this.highlightSprite) {
69978             this.highlightSprite.hide(true);
69979         }
69980     },
69981
69982     // @private
69983     hideAll: function() {
69984         if (!isNaN(this._index)) {
69985             this.__excludes[this._index] = true;
69986             this.areas[this._index].hide(true);
69987             this.drawSeries();
69988         }
69989     },
69990
69991     // @private
69992     showAll: function() {
69993         if (!isNaN(this._index)) {
69994             this.__excludes[this._index] = false;
69995             this.areas[this._index].show(true);
69996             this.drawSeries();
69997         }
69998     },
69999
70000     /**
70001      * Returns the color of the series (to be displayed as color for the series legend item).
70002      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70003      */
70004     getLegendColor: function(index) {
70005         var me = this;
70006         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70007     }
70008 });
70009 /**
70010  * @class Ext.chart.series.Area
70011  * @extends Ext.chart.series.Cartesian
70012  *
70013  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
70014  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
70015  * documentation for more information. A typical configuration object for the area series could be:
70016  *
70017  *     @example
70018  *     var store = Ext.create('Ext.data.JsonStore', {
70019  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70020  *         data: [
70021  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70022  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70023  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70024  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70025  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70026  *         ]
70027  *     });
70028  *
70029  *     Ext.create('Ext.chart.Chart', {
70030  *         renderTo: Ext.getBody(),
70031  *         width: 500,
70032  *         height: 300,
70033  *         store: store,
70034  *         axes: [
70035  *             {
70036  *                 type: 'Numeric',
70037  *                 grid: true,
70038  *                 position: 'left',
70039  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70040  *                 title: 'Sample Values',
70041  *                 grid: {
70042  *                     odd: {
70043  *                         opacity: 1,
70044  *                         fill: '#ddd',
70045  *                         stroke: '#bbb',
70046  *                         'stroke-width': 1
70047  *                     }
70048  *                 },
70049  *                 minimum: 0,
70050  *                 adjustMinimumByMajorUnit: 0
70051  *             },
70052  *             {
70053  *                 type: 'Category',
70054  *                 position: 'bottom',
70055  *                 fields: ['name'],
70056  *                 title: 'Sample Metrics',
70057  *                 grid: true,
70058  *                 label: {
70059  *                     rotate: {
70060  *                         degrees: 315
70061  *                     }
70062  *                 }
70063  *             }
70064  *         ],
70065  *         series: [{
70066  *             type: 'area',
70067  *             highlight: false,
70068  *             axis: 'left',
70069  *             xField: 'name',
70070  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70071  *             style: {
70072  *                 opacity: 0.93
70073  *             }
70074  *         }]
70075  *     });
70076  *
70077  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70078  * 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,
70079  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70080  * to the style object.
70081  *
70082  * @xtype area
70083  */
70084 Ext.define('Ext.chart.series.Area', {
70085
70086     /* Begin Definitions */
70087
70088     extend: 'Ext.chart.series.Cartesian',
70089
70090     alias: 'series.area',
70091
70092     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70093
70094     /* End Definitions */
70095
70096     type: 'area',
70097
70098     // @private Area charts are alyways stacked
70099     stacked: true,
70100
70101     /**
70102      * @cfg {Object} style
70103      * Append styling properties to this object for it to override theme properties.
70104      */
70105     style: {},
70106
70107     constructor: function(config) {
70108         this.callParent(arguments);
70109         var me = this,
70110             surface = me.chart.surface,
70111             i, l;
70112         Ext.apply(me, config, {
70113             __excludes: [],
70114             highlightCfg: {
70115                 lineWidth: 3,
70116                 stroke: '#55c',
70117                 opacity: 0.8,
70118                 color: '#f00'
70119             }
70120         });
70121         if (me.highlight) {
70122             me.highlightSprite = surface.add({
70123                 type: 'path',
70124                 path: ['M', 0, 0],
70125                 zIndex: 1000,
70126                 opacity: 0.3,
70127                 lineWidth: 5,
70128                 hidden: true,
70129                 stroke: '#444'
70130             });
70131         }
70132         me.group = surface.getGroup(me.seriesId);
70133     },
70134
70135     // @private Shrinks dataSets down to a smaller size
70136     shrink: function(xValues, yValues, size) {
70137         var len = xValues.length,
70138             ratio = Math.floor(len / size),
70139             i, j,
70140             xSum = 0,
70141             yCompLen = this.areas.length,
70142             ySum = [],
70143             xRes = [],
70144             yRes = [];
70145         //initialize array
70146         for (j = 0; j < yCompLen; ++j) {
70147             ySum[j] = 0;
70148         }
70149         for (i = 0; i < len; ++i) {
70150             xSum += xValues[i];
70151             for (j = 0; j < yCompLen; ++j) {
70152                 ySum[j] += yValues[i][j];
70153             }
70154             if (i % ratio == 0) {
70155                 //push averages
70156                 xRes.push(xSum/ratio);
70157                 for (j = 0; j < yCompLen; ++j) {
70158                     ySum[j] /= ratio;
70159                 }
70160                 yRes.push(ySum);
70161                 //reset sum accumulators
70162                 xSum = 0;
70163                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70164                     ySum[j] = 0;
70165                 }
70166             }
70167         }
70168         return {
70169             x: xRes,
70170             y: yRes
70171         };
70172     },
70173
70174     // @private Get chart and data boundaries
70175     getBounds: function() {
70176         var me = this,
70177             chart = me.chart,
70178             store = chart.getChartStore(),
70179             areas = [].concat(me.yField),
70180             areasLen = areas.length,
70181             xValues = [],
70182             yValues = [],
70183             infinity = Infinity,
70184             minX = infinity,
70185             minY = infinity,
70186             maxX = -infinity,
70187             maxY = -infinity,
70188             math = Math,
70189             mmin = math.min,
70190             mmax = math.max,
70191             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70192
70193         me.setBBox();
70194         bbox = me.bbox;
70195
70196         // Run through the axis
70197         if (me.axis) {
70198             axis = chart.axes.get(me.axis);
70199             if (axis) {
70200                 out = axis.calcEnds();
70201                 minY = out.from || axis.prevMin;
70202                 maxY = mmax(out.to || axis.prevMax, 0);
70203             }
70204         }
70205
70206         if (me.yField && !Ext.isNumber(minY)) {
70207             axis = Ext.create('Ext.chart.axis.Axis', {
70208                 chart: chart,
70209                 fields: [].concat(me.yField)
70210             });
70211             out = axis.calcEnds();
70212             minY = out.from || axis.prevMin;
70213             maxY = mmax(out.to || axis.prevMax, 0);
70214         }
70215
70216         if (!Ext.isNumber(minY)) {
70217             minY = 0;
70218         }
70219         if (!Ext.isNumber(maxY)) {
70220             maxY = 0;
70221         }
70222
70223         store.each(function(record, i) {
70224             xValue = record.get(me.xField);
70225             yValue = [];
70226             if (typeof xValue != 'number') {
70227                 xValue = i;
70228             }
70229             xValues.push(xValue);
70230             acumY = 0;
70231             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70232                 areaElem = record.get(areas[areaIndex]);
70233                 if (typeof areaElem == 'number') {
70234                     minY = mmin(minY, areaElem);
70235                     yValue.push(areaElem);
70236                     acumY += areaElem;
70237                 }
70238             }
70239             minX = mmin(minX, xValue);
70240             maxX = mmax(maxX, xValue);
70241             maxY = mmax(maxY, acumY);
70242             yValues.push(yValue);
70243         }, me);
70244
70245         xScale = bbox.width / ((maxX - minX) || 1);
70246         yScale = bbox.height / ((maxY - minY) || 1);
70247
70248         ln = xValues.length;
70249         if ((ln > bbox.width) && me.areas) {
70250             sumValues = me.shrink(xValues, yValues, bbox.width);
70251             xValues = sumValues.x;
70252             yValues = sumValues.y;
70253         }
70254
70255         return {
70256             bbox: bbox,
70257             minX: minX,
70258             minY: minY,
70259             xValues: xValues,
70260             yValues: yValues,
70261             xScale: xScale,
70262             yScale: yScale,
70263             areasLen: areasLen
70264         };
70265     },
70266
70267     // @private Build an array of paths for the chart
70268     getPaths: function() {
70269         var me = this,
70270             chart = me.chart,
70271             store = chart.getChartStore(),
70272             first = true,
70273             bounds = me.getBounds(),
70274             bbox = bounds.bbox,
70275             items = me.items = [],
70276             componentPaths = [],
70277             componentPath,
70278             paths = [],
70279             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70280
70281         ln = bounds.xValues.length;
70282         // Start the path
70283         for (i = 0; i < ln; i++) {
70284             xValue = bounds.xValues[i];
70285             yValue = bounds.yValues[i];
70286             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70287             acumY = 0;
70288             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70289                 // Excluded series
70290                 if (me.__excludes[areaIndex]) {
70291                     continue;
70292                 }
70293                 if (!componentPaths[areaIndex]) {
70294                     componentPaths[areaIndex] = [];
70295                 }
70296                 areaElem = yValue[areaIndex];
70297                 acumY += areaElem;
70298                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70299                 if (!paths[areaIndex]) {
70300                     paths[areaIndex] = ['M', x, y];
70301                     componentPaths[areaIndex].push(['L', x, y]);
70302                 } else {
70303                     paths[areaIndex].push('L', x, y);
70304                     componentPaths[areaIndex].push(['L', x, y]);
70305                 }
70306                 if (!items[areaIndex]) {
70307                     items[areaIndex] = {
70308                         pointsUp: [],
70309                         pointsDown: [],
70310                         series: me
70311                     };
70312                 }
70313                 items[areaIndex].pointsUp.push([x, y]);
70314             }
70315         }
70316
70317         // Close the paths
70318         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70319             // Excluded series
70320             if (me.__excludes[areaIndex]) {
70321                 continue;
70322             }
70323             path = paths[areaIndex];
70324             // Close bottom path to the axis
70325             if (areaIndex == 0 || first) {
70326                 first = false;
70327                 path.push('L', x, bbox.y + bbox.height,
70328                           'L', bbox.x, bbox.y + bbox.height,
70329                           'Z');
70330             }
70331             // Close other paths to the one before them
70332             else {
70333                 componentPath = componentPaths[prevAreaIndex];
70334                 componentPath.reverse();
70335                 path.push('L', x, componentPath[0][2]);
70336                 for (i = 0; i < ln; i++) {
70337                     path.push(componentPath[i][0],
70338                               componentPath[i][1],
70339                               componentPath[i][2]);
70340                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
70341                 }
70342                 path.push('L', bbox.x, path[2], 'Z');
70343             }
70344             prevAreaIndex = areaIndex;
70345         }
70346         return {
70347             paths: paths,
70348             areasLen: bounds.areasLen
70349         };
70350     },
70351
70352     /**
70353      * Draws the series for the current chart.
70354      */
70355     drawSeries: function() {
70356         var me = this,
70357             chart = me.chart,
70358             store = chart.getChartStore(),
70359             surface = chart.surface,
70360             animate = chart.animate,
70361             group = me.group,
70362             endLineStyle = Ext.apply(me.seriesStyle, me.style),
70363             colorArrayStyle = me.colorArrayStyle,
70364             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
70365             areaIndex, areaElem, paths, path, rendererAttributes;
70366
70367         me.unHighlightItem();
70368         me.cleanHighlights();
70369
70370         if (!store || !store.getCount()) {
70371             return;
70372         }
70373
70374         paths = me.getPaths();
70375
70376         if (!me.areas) {
70377             me.areas = [];
70378         }
70379
70380         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
70381             // Excluded series
70382             if (me.__excludes[areaIndex]) {
70383                 continue;
70384             }
70385             if (!me.areas[areaIndex]) {
70386                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
70387                     type: 'path',
70388                     group: group,
70389                     // 'clip-rect': me.clipBox,
70390                     path: paths.paths[areaIndex],
70391                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
70392                     fill: colorArrayStyle[areaIndex % colorArrayLength]
70393                 }, endLineStyle || {}));
70394             }
70395             areaElem = me.areas[areaIndex];
70396             path = paths.paths[areaIndex];
70397             if (animate) {
70398                 //Add renderer to line. There is not a unique record associated with this.
70399                 rendererAttributes = me.renderer(areaElem, false, {
70400                     path: path,
70401                     // 'clip-rect': me.clipBox,
70402                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70403                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70404                 }, areaIndex, store);
70405                 //fill should not be used here but when drawing the special fill path object
70406                 me.animation = me.onAnimate(areaElem, {
70407                     to: rendererAttributes
70408                 });
70409             } else {
70410                 rendererAttributes = me.renderer(areaElem, false, {
70411                     path: path,
70412                     // 'clip-rect': me.clipBox,
70413                     hidden: false,
70414                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70415                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70416                 }, areaIndex, store);
70417                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
70418             }
70419         }
70420         me.renderLabels();
70421         me.renderCallouts();
70422     },
70423
70424     // @private
70425     onAnimate: function(sprite, attr) {
70426         sprite.show();
70427         return this.callParent(arguments);
70428     },
70429
70430     // @private
70431     onCreateLabel: function(storeItem, item, i, display) {
70432         var me = this,
70433             group = me.labelsGroup,
70434             config = me.label,
70435             bbox = me.bbox,
70436             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
70437
70438         return me.chart.surface.add(Ext.apply({
70439             'type': 'text',
70440             'text-anchor': 'middle',
70441             'group': group,
70442             'x': item.point[0],
70443             'y': bbox.y + bbox.height / 2
70444         }, endLabelStyle || {}));
70445     },
70446
70447     // @private
70448     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
70449         var me = this,
70450             chart = me.chart,
70451             resizing = chart.resizing,
70452             config = me.label,
70453             format = config.renderer,
70454             field = config.field,
70455             bbox = me.bbox,
70456             x = item.point[0],
70457             y = item.point[1],
70458             bb, width, height;
70459
70460         label.setAttributes({
70461             text: format(storeItem.get(field[index])),
70462             hidden: true
70463         }, true);
70464
70465         bb = label.getBBox();
70466         width = bb.width / 2;
70467         height = bb.height / 2;
70468
70469         x = x - width < bbox.x? bbox.x + width : x;
70470         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70471         y = y - height < bbox.y? bbox.y + height : y;
70472         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70473
70474         if (me.chart.animate && !me.chart.resizing) {
70475             label.show(true);
70476             me.onAnimate(label, {
70477                 to: {
70478                     x: x,
70479                     y: y
70480                 }
70481             });
70482         } else {
70483             label.setAttributes({
70484                 x: x,
70485                 y: y
70486             }, true);
70487             if (resizing) {
70488                 me.animation.on('afteranimate', function() {
70489                     label.show(true);
70490                 });
70491             } else {
70492                 label.show(true);
70493             }
70494         }
70495     },
70496
70497     // @private
70498     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
70499         var me = this,
70500             chart = me.chart,
70501             surface = chart.surface,
70502             resizing = chart.resizing,
70503             config = me.callouts,
70504             items = me.items,
70505             prev = (i == 0) ? false : items[i -1].point,
70506             next = (i == items.length -1) ? false : items[i +1].point,
70507             cur = item.point,
70508             dir, norm, normal, a, aprev, anext,
70509             bbox = callout.label.getBBox(),
70510             offsetFromViz = 30,
70511             offsetToSide = 10,
70512             offsetBox = 3,
70513             boxx, boxy, boxw, boxh,
70514             p, clipRect = me.clipRect,
70515             x, y;
70516
70517         //get the right two points
70518         if (!prev) {
70519             prev = cur;
70520         }
70521         if (!next) {
70522             next = cur;
70523         }
70524         a = (next[1] - prev[1]) / (next[0] - prev[0]);
70525         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
70526         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
70527
70528         norm = Math.sqrt(1 + a * a);
70529         dir = [1 / norm, a / norm];
70530         normal = [-dir[1], dir[0]];
70531
70532         //keep the label always on the outer part of the "elbow"
70533         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
70534             normal[0] *= -1;
70535             normal[1] *= -1;
70536         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
70537             normal[0] *= -1;
70538             normal[1] *= -1;
70539         }
70540
70541         //position
70542         x = cur[0] + normal[0] * offsetFromViz;
70543         y = cur[1] + normal[1] * offsetFromViz;
70544
70545         //box position and dimensions
70546         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70547         boxy = y - bbox.height /2 - offsetBox;
70548         boxw = bbox.width + 2 * offsetBox;
70549         boxh = bbox.height + 2 * offsetBox;
70550
70551         //now check if we're out of bounds and invert the normal vector correspondingly
70552         //this may add new overlaps between labels (but labels won't be out of bounds).
70553         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70554             normal[0] *= -1;
70555         }
70556         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70557             normal[1] *= -1;
70558         }
70559
70560         //update positions
70561         x = cur[0] + normal[0] * offsetFromViz;
70562         y = cur[1] + normal[1] * offsetFromViz;
70563
70564         //update box position and dimensions
70565         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70566         boxy = y - bbox.height /2 - offsetBox;
70567         boxw = bbox.width + 2 * offsetBox;
70568         boxh = bbox.height + 2 * offsetBox;
70569
70570         //set the line from the middle of the pie to the box.
70571         callout.lines.setAttributes({
70572             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70573         }, true);
70574         //set box position
70575         callout.box.setAttributes({
70576             x: boxx,
70577             y: boxy,
70578             width: boxw,
70579             height: boxh
70580         }, true);
70581         //set text position
70582         callout.label.setAttributes({
70583             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70584             y: y
70585         }, true);
70586         for (p in callout) {
70587             callout[p].show(true);
70588         }
70589     },
70590
70591     isItemInPoint: function(x, y, item, i) {
70592         var me = this,
70593             pointsUp = item.pointsUp,
70594             pointsDown = item.pointsDown,
70595             abs = Math.abs,
70596             dist = Infinity, p, pln, point;
70597
70598         for (p = 0, pln = pointsUp.length; p < pln; p++) {
70599             point = [pointsUp[p][0], pointsUp[p][1]];
70600             if (dist > abs(x - point[0])) {
70601                 dist = abs(x - point[0]);
70602             } else {
70603                 point = pointsUp[p -1];
70604                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
70605                     item.storeIndex = p -1;
70606                     item.storeField = me.yField[i];
70607                     item.storeItem = me.chart.store.getAt(p -1);
70608                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
70609                     return true;
70610                 } else {
70611                     break;
70612                 }
70613             }
70614         }
70615         return false;
70616     },
70617
70618     /**
70619      * Highlight this entire series.
70620      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70621      */
70622     highlightSeries: function() {
70623         var area, to, fillColor;
70624         if (this._index !== undefined) {
70625             area = this.areas[this._index];
70626             if (area.__highlightAnim) {
70627                 area.__highlightAnim.paused = true;
70628             }
70629             area.__highlighted = true;
70630             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
70631             area.__prevFill = area.__prevFill || area.attr.fill;
70632             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
70633             fillColor = Ext.draw.Color.fromString(area.__prevFill);
70634             to = {
70635                 lineWidth: (area.__prevLineWidth || 0) + 2
70636             };
70637             if (fillColor) {
70638                 to.fill = fillColor.getLighter(0.2).toString();
70639             }
70640             else {
70641                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
70642             }
70643             if (this.chart.animate) {
70644                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
70645                     target: area,
70646                     to: to
70647                 }, this.chart.animate));
70648             }
70649             else {
70650                 area.setAttributes(to, true);
70651             }
70652         }
70653     },
70654
70655     /**
70656      * UnHighlight this entire series.
70657      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70658      */
70659     unHighlightSeries: function() {
70660         var area;
70661         if (this._index !== undefined) {
70662             area = this.areas[this._index];
70663             if (area.__highlightAnim) {
70664                 area.__highlightAnim.paused = true;
70665             }
70666             if (area.__highlighted) {
70667                 area.__highlighted = false;
70668                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
70669                     target: area,
70670                     to: {
70671                         fill: area.__prevFill,
70672                         opacity: area.__prevOpacity,
70673                         lineWidth: area.__prevLineWidth
70674                     }
70675                 });
70676             }
70677         }
70678     },
70679
70680     /**
70681      * Highlight the specified item. If no item is provided the whole series will be highlighted.
70682      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70683      */
70684     highlightItem: function(item) {
70685         var me = this,
70686             points, path;
70687         if (!item) {
70688             this.highlightSeries();
70689             return;
70690         }
70691         points = item._points;
70692         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
70693                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
70694         me.highlightSprite.setAttributes({
70695             path: path,
70696             hidden: false
70697         }, true);
70698     },
70699
70700     /**
70701      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70702      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70703      */
70704     unHighlightItem: function(item) {
70705         if (!item) {
70706             this.unHighlightSeries();
70707         }
70708
70709         if (this.highlightSprite) {
70710             this.highlightSprite.hide(true);
70711         }
70712     },
70713
70714     // @private
70715     hideAll: function() {
70716         if (!isNaN(this._index)) {
70717             this.__excludes[this._index] = true;
70718             this.areas[this._index].hide(true);
70719             this.drawSeries();
70720         }
70721     },
70722
70723     // @private
70724     showAll: function() {
70725         if (!isNaN(this._index)) {
70726             this.__excludes[this._index] = false;
70727             this.areas[this._index].show(true);
70728             this.drawSeries();
70729         }
70730     },
70731
70732     /**
70733      * Returns the color of the series (to be displayed as color for the series legend item).
70734      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70735      */
70736     getLegendColor: function(index) {
70737         var me = this;
70738         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70739     }
70740 });
70741
70742 /**
70743  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
70744  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
70745  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
70746  * A typical configuration object for the bar series could be:
70747  *
70748  *     @example
70749  *     var store = Ext.create('Ext.data.JsonStore', {
70750  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70751  *         data: [
70752  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70753  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70754  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70755  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70756  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70757  *         ]
70758  *     });
70759  *
70760  *     Ext.create('Ext.chart.Chart', {
70761  *         renderTo: Ext.getBody(),
70762  *         width: 500,
70763  *         height: 300,
70764  *         animate: true,
70765  *         store: store,
70766  *         axes: [{
70767  *             type: 'Numeric',
70768  *             position: 'bottom',
70769  *             fields: ['data1'],
70770  *             label: {
70771  *                 renderer: Ext.util.Format.numberRenderer('0,0')
70772  *             },
70773  *             title: 'Sample Values',
70774  *             grid: true,
70775  *             minimum: 0
70776  *         }, {
70777  *             type: 'Category',
70778  *             position: 'left',
70779  *             fields: ['name'],
70780  *             title: 'Sample Metrics'
70781  *         }],
70782  *         series: [{
70783  *             type: 'bar',
70784  *             axis: 'bottom',
70785  *             highlight: true,
70786  *             tips: {
70787  *               trackMouse: true,
70788  *               width: 140,
70789  *               height: 28,
70790  *               renderer: function(storeItem, item) {
70791  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
70792  *               }
70793  *             },
70794  *             label: {
70795  *               display: 'insideEnd',
70796  *                 field: 'data1',
70797  *                 renderer: Ext.util.Format.numberRenderer('0'),
70798  *                 orientation: 'horizontal',
70799  *                 color: '#333',
70800  *                 'text-anchor': 'middle'
70801  *             },
70802  *             xField: 'name',
70803  *             yField: ['data1']
70804  *         }]
70805  *     });
70806  *
70807  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
70808  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
70809  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
70810  * to display the information found in the `data1` property of each element store, to render a formated text with the
70811  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
70812  * other styles like `color`, `text-anchor`, etc.
70813  */
70814 Ext.define('Ext.chart.series.Bar', {
70815
70816     /* Begin Definitions */
70817
70818     extend: 'Ext.chart.series.Cartesian',
70819
70820     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
70821
70822     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
70823
70824     /* End Definitions */
70825
70826     type: 'bar',
70827
70828     alias: 'series.bar',
70829     /**
70830      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
70831      */
70832     column: false,
70833
70834     /**
70835      * @cfg style Style properties that will override the theming series styles.
70836      */
70837     style: {},
70838
70839     /**
70840      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
70841      */
70842     gutter: 38.2,
70843
70844     /**
70845      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
70846      */
70847     groupGutter: 38.2,
70848
70849     /**
70850      * @cfg {Number} xPadding Padding between the left/right axes and the bars
70851      */
70852     xPadding: 0,
70853
70854     /**
70855      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
70856      */
70857     yPadding: 10,
70858
70859     constructor: function(config) {
70860         this.callParent(arguments);
70861         var me = this,
70862             surface = me.chart.surface,
70863             shadow = me.chart.shadow,
70864             i, l;
70865         Ext.apply(me, config, {
70866             highlightCfg: {
70867                 lineWidth: 3,
70868                 stroke: '#55c',
70869                 opacity: 0.8,
70870                 color: '#f00'
70871             },
70872
70873             shadowAttributes: [{
70874                 "stroke-width": 6,
70875                 "stroke-opacity": 0.05,
70876                 stroke: 'rgb(200, 200, 200)',
70877                 translate: {
70878                     x: 1.2,
70879                     y: 1.2
70880                 }
70881             }, {
70882                 "stroke-width": 4,
70883                 "stroke-opacity": 0.1,
70884                 stroke: 'rgb(150, 150, 150)',
70885                 translate: {
70886                     x: 0.9,
70887                     y: 0.9
70888                 }
70889             }, {
70890                 "stroke-width": 2,
70891                 "stroke-opacity": 0.15,
70892                 stroke: 'rgb(100, 100, 100)',
70893                 translate: {
70894                     x: 0.6,
70895                     y: 0.6
70896                 }
70897             }]
70898         });
70899         me.group = surface.getGroup(me.seriesId + '-bars');
70900         if (shadow) {
70901             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
70902                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
70903             }
70904         }
70905     },
70906
70907     // @private sets the bar girth.
70908     getBarGirth: function() {
70909         var me = this,
70910             store = me.chart.getChartStore(),
70911             column = me.column,
70912             ln = store.getCount(),
70913             gutter = me.gutter / 100;
70914
70915         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
70916     },
70917
70918     // @private returns the gutters.
70919     getGutters: function() {
70920         var me = this,
70921             column = me.column,
70922             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
70923         return me.column ? [gutter, 0] : [0, gutter];
70924     },
70925
70926     // @private Get chart and data boundaries
70927     getBounds: function() {
70928         var me = this,
70929             chart = me.chart,
70930             store = chart.getChartStore(),
70931             bars = [].concat(me.yField),
70932             barsLen = bars.length,
70933             groupBarsLen = barsLen,
70934             groupGutter = me.groupGutter / 100,
70935             column = me.column,
70936             xPadding = me.xPadding,
70937             yPadding = me.yPadding,
70938             stacked = me.stacked,
70939             barWidth = me.getBarGirth(),
70940             math = Math,
70941             mmax = math.max,
70942             mabs = math.abs,
70943             groupBarWidth, bbox, minY, maxY, axis, out,
70944             scale, zero, total, rec, j, plus, minus;
70945
70946         me.setBBox(true);
70947         bbox = me.bbox;
70948
70949         //Skip excluded series
70950         if (me.__excludes) {
70951             for (j = 0, total = me.__excludes.length; j < total; j++) {
70952                 if (me.__excludes[j]) {
70953                     groupBarsLen--;
70954                 }
70955             }
70956         }
70957
70958         if (me.axis) {
70959             axis = chart.axes.get(me.axis);
70960             if (axis) {
70961                 out = axis.calcEnds();
70962                 minY = out.from;
70963                 maxY = out.to;
70964             }
70965         }
70966
70967         if (me.yField && !Ext.isNumber(minY)) {
70968             axis = Ext.create('Ext.chart.axis.Axis', {
70969                 chart: chart,
70970                 fields: [].concat(me.yField)
70971             });
70972             out = axis.calcEnds();
70973             minY = out.from;
70974             maxY = out.to;
70975         }
70976
70977         if (!Ext.isNumber(minY)) {
70978             minY = 0;
70979         }
70980         if (!Ext.isNumber(maxY)) {
70981             maxY = 0;
70982         }
70983         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
70984         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
70985         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
70986
70987         if (stacked) {
70988             total = [[], []];
70989             store.each(function(record, i) {
70990                 total[0][i] = total[0][i] || 0;
70991                 total[1][i] = total[1][i] || 0;
70992                 for (j = 0; j < barsLen; j++) {
70993                     if (me.__excludes && me.__excludes[j]) {
70994                         continue;
70995                     }
70996                     rec = record.get(bars[j]);
70997                     total[+(rec > 0)][i] += mabs(rec);
70998                 }
70999             });
71000             total[+(maxY > 0)].push(mabs(maxY));
71001             total[+(minY > 0)].push(mabs(minY));
71002             minus = mmax.apply(math, total[0]);
71003             plus = mmax.apply(math, total[1]);
71004             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
71005             zero = zero + minus * scale * (column ? -1 : 1);
71006         }
71007         else if (minY / maxY < 0) {
71008             zero = zero - minY * scale * (column ? -1 : 1);
71009         }
71010         return {
71011             bars: bars,
71012             bbox: bbox,
71013             barsLen: barsLen,
71014             groupBarsLen: groupBarsLen,
71015             barWidth: barWidth,
71016             groupBarWidth: groupBarWidth,
71017             scale: scale,
71018             zero: zero,
71019             xPadding: xPadding,
71020             yPadding: yPadding,
71021             signed: minY / maxY < 0,
71022             minY: minY,
71023             maxY: maxY
71024         };
71025     },
71026
71027     // @private Build an array of paths for the chart
71028     getPaths: function() {
71029         var me = this,
71030             chart = me.chart,
71031             store = chart.getChartStore(),
71032             bounds = me.bounds = me.getBounds(),
71033             items = me.items = [],
71034             gutter = me.gutter / 100,
71035             groupGutter = me.groupGutter / 100,
71036             animate = chart.animate,
71037             column = me.column,
71038             group = me.group,
71039             enableShadows = chart.shadow,
71040             shadowGroups = me.shadowGroups,
71041             shadowAttributes = me.shadowAttributes,
71042             shadowGroupsLn = shadowGroups.length,
71043             bbox = bounds.bbox,
71044             xPadding = me.xPadding,
71045             yPadding = me.yPadding,
71046             stacked = me.stacked,
71047             barsLen = bounds.barsLen,
71048             colors = me.colorArrayStyle,
71049             colorLength = colors && colors.length || 0,
71050             math = Math,
71051             mmax = math.max,
71052             mmin = math.min,
71053             mabs = math.abs,
71054             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
71055             shadowIndex, shadow, sprite, offset, floorY;
71056
71057         store.each(function(record, i, total) {
71058             bottom = bounds.zero;
71059             top = bounds.zero;
71060             totalDim = 0;
71061             totalNegDim = 0;
71062             hasShadow = false;
71063             for (j = 0, counter = 0; j < barsLen; j++) {
71064                 // Excluded series
71065                 if (me.__excludes && me.__excludes[j]) {
71066                     continue;
71067                 }
71068                 yValue = record.get(bounds.bars[j]);
71069                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
71070                 barAttr = {
71071                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
71072                 };
71073                 if (column) {
71074                     Ext.apply(barAttr, {
71075                         height: height,
71076                         width: mmax(bounds.groupBarWidth, 0),
71077                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
71078                         y: bottom - height
71079                     });
71080                 }
71081                 else {
71082                     // draw in reverse order
71083                     offset = (total - 1) - i;
71084                     Ext.apply(barAttr, {
71085                         height: mmax(bounds.groupBarWidth, 0),
71086                         width: height + (bottom == bounds.zero),
71087                         x: bottom + (bottom != bounds.zero),
71088                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
71089                     });
71090                 }
71091                 if (height < 0) {
71092                     if (column) {
71093                         barAttr.y = top;
71094                         barAttr.height = mabs(height);
71095                     } else {
71096                         barAttr.x = top + height;
71097                         barAttr.width = mabs(height);
71098                     }
71099                 }
71100                 if (stacked) {
71101                     if (height < 0) {
71102                         top += height * (column ? -1 : 1);
71103                     } else {
71104                         bottom += height * (column ? -1 : 1);
71105                     }
71106                     totalDim += mabs(height);
71107                     if (height < 0) {
71108                         totalNegDim += mabs(height);
71109                     }
71110                 }
71111                 barAttr.x = Math.floor(barAttr.x) + 1;
71112                 floorY = Math.floor(barAttr.y);
71113                 if (!Ext.isIE9 && barAttr.y > floorY) {
71114                     floorY--;
71115                 }
71116                 barAttr.y = floorY;
71117                 barAttr.width = Math.floor(barAttr.width);
71118                 barAttr.height = Math.floor(barAttr.height);
71119                 items.push({
71120                     series: me,
71121                     storeItem: record,
71122                     value: [record.get(me.xField), yValue],
71123                     attr: barAttr,
71124                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
71125                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
71126                 });
71127                 // When resizing, reset before animating
71128                 if (animate && chart.resizing) {
71129                     attrs = column ? {
71130                         x: barAttr.x,
71131                         y: bounds.zero,
71132                         width: barAttr.width,
71133                         height: 0
71134                     } : {
71135                         x: bounds.zero,
71136                         y: barAttr.y,
71137                         width: 0,
71138                         height: barAttr.height
71139                     };
71140                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
71141                         hasShadow = true;
71142                         //update shadows
71143                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71144                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
71145                             if (shadow) {
71146                                 shadow.setAttributes(attrs, true);
71147                             }
71148                         }
71149                     }
71150                     //update sprite position and width/height
71151                     sprite = group.getAt(i * barsLen + j);
71152                     if (sprite) {
71153                         sprite.setAttributes(attrs, true);
71154                     }
71155                 }
71156                 counter++;
71157             }
71158             if (stacked && items.length) {
71159                 items[i * counter].totalDim = totalDim;
71160                 items[i * counter].totalNegDim = totalNegDim;
71161             }
71162         }, me);
71163     },
71164
71165     // @private render/setAttributes on the shadows
71166     renderShadows: function(i, barAttr, baseAttrs, bounds) {
71167         var me = this,
71168             chart = me.chart,
71169             surface = chart.surface,
71170             animate = chart.animate,
71171             stacked = me.stacked,
71172             shadowGroups = me.shadowGroups,
71173             shadowAttributes = me.shadowAttributes,
71174             shadowGroupsLn = shadowGroups.length,
71175             store = chart.getChartStore(),
71176             column = me.column,
71177             items = me.items,
71178             shadows = [],
71179             zero = bounds.zero,
71180             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
71181
71182         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
71183             j = i / bounds.groupBarsLen;
71184             //create shadows
71185             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71186                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
71187                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
71188                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
71189                 if (!shadow) {
71190                     shadow = surface.add(Ext.apply({
71191                         type: 'rect',
71192                         group: shadowGroups[shadowIndex]
71193                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
71194                 }
71195                 if (stacked) {
71196                     totalDim = items[i].totalDim;
71197                     totalNegDim = items[i].totalNegDim;
71198                     if (column) {
71199                         shadowBarAttr.y = zero - totalNegDim;
71200                         shadowBarAttr.height = totalDim;
71201                     }
71202                     else {
71203                         shadowBarAttr.x = zero - totalNegDim;
71204                         shadowBarAttr.width = totalDim;
71205                     }
71206                 }
71207                 if (animate) {
71208                     if (!stacked) {
71209                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
71210                         me.onAnimate(shadow, { to: rendererAttributes });
71211                     }
71212                     else {
71213                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
71214                         shadow.setAttributes(rendererAttributes, true);
71215                     }
71216                 }
71217                 else {
71218                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
71219                     shadow.setAttributes(rendererAttributes, true);
71220                 }
71221                 shadows.push(shadow);
71222             }
71223         }
71224         return shadows;
71225     },
71226
71227     /**
71228      * Draws the series for the current chart.
71229      */
71230     drawSeries: function() {
71231         var me = this,
71232             chart = me.chart,
71233             store = chart.getChartStore(),
71234             surface = chart.surface,
71235             animate = chart.animate,
71236             stacked = me.stacked,
71237             column = me.column,
71238             enableShadows = chart.shadow,
71239             shadowGroups = me.shadowGroups,
71240             shadowGroupsLn = shadowGroups.length,
71241             group = me.group,
71242             seriesStyle = me.seriesStyle,
71243             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
71244             bounds, endSeriesStyle, barAttr, attrs, anim;
71245
71246         if (!store || !store.getCount()) {
71247             return;
71248         }
71249
71250         //fill colors are taken from the colors array.
71251         delete seriesStyle.fill;
71252         endSeriesStyle = Ext.apply(seriesStyle, this.style);
71253         me.unHighlightItem();
71254         me.cleanHighlights();
71255
71256         me.getPaths();
71257         bounds = me.bounds;
71258         items = me.items;
71259
71260         baseAttrs = column ? {
71261             y: bounds.zero,
71262             height: 0
71263         } : {
71264             x: bounds.zero,
71265             width: 0
71266         };
71267         ln = items.length;
71268         // Create new or reuse sprites and animate/display
71269         for (i = 0; i < ln; i++) {
71270             sprite = group.getAt(i);
71271             barAttr = items[i].attr;
71272
71273             if (enableShadows) {
71274                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
71275             }
71276
71277             // Create a new sprite if needed (no height)
71278             if (!sprite) {
71279                 attrs = Ext.apply({}, baseAttrs, barAttr);
71280                 attrs = Ext.apply(attrs, endSeriesStyle || {});
71281                 sprite = surface.add(Ext.apply({}, {
71282                     type: 'rect',
71283                     group: group
71284                 }, attrs));
71285             }
71286             if (animate) {
71287                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
71288                 sprite._to = rendererAttributes;
71289                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
71290                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
71291                     j = i / bounds.barsLen;
71292                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71293                         anim.on('afteranimate', function() {
71294                             this.show(true);
71295                         }, shadowGroups[shadowIndex].getAt(j));
71296                     }
71297                 }
71298             }
71299             else {
71300                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
71301                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
71302             }
71303             items[i].sprite = sprite;
71304         }
71305
71306         // Hide unused sprites
71307         ln = group.getCount();
71308         for (j = i; j < ln; j++) {
71309             group.getAt(j).hide(true);
71310         }
71311         // Hide unused shadows
71312         if (enableShadows) {
71313             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71314                 shadowGroup = shadowGroups[shadowIndex];
71315                 ln = shadowGroup.getCount();
71316                 for (j = i; j < ln; j++) {
71317                     shadowGroup.getAt(j).hide(true);
71318                 }
71319             }
71320         }
71321         me.renderLabels();
71322     },
71323
71324     // @private handled when creating a label.
71325     onCreateLabel: function(storeItem, item, i, display) {
71326         var me = this,
71327             surface = me.chart.surface,
71328             group = me.labelsGroup,
71329             config = me.label,
71330             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
71331             sprite;
71332         return surface.add(Ext.apply({
71333             type: 'text',
71334             group: group
71335         }, endLabelStyle || {}));
71336     },
71337
71338     // @private callback used when placing a label.
71339     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
71340         // Determine the label's final position. Starts with the configured preferred value but
71341         // may get flipped from inside to outside or vice-versa depending on space.
71342         var me = this,
71343             opt = me.bounds,
71344             groupBarWidth = opt.groupBarWidth,
71345             column = me.column,
71346             chart = me.chart,
71347             chartBBox = chart.chartBBox,
71348             resizing = chart.resizing,
71349             xValue = item.value[0],
71350             yValue = item.value[1],
71351             attr = item.attr,
71352             config = me.label,
71353             rotate = config.orientation == 'vertical',
71354             field = [].concat(config.field),
71355             format = config.renderer,
71356             text = format(storeItem.get(field[index])),
71357             size = me.getLabelSize(text),
71358             width = size.width,
71359             height = size.height,
71360             zero = opt.zero,
71361             outside = 'outside',
71362             insideStart = 'insideStart',
71363             insideEnd = 'insideEnd',
71364             offsetX = 10,
71365             offsetY = 6,
71366             signed = opt.signed,
71367             x, y, finalAttr;
71368
71369         label.setAttributes({
71370             text: text
71371         });
71372
71373         label.isOutside = false;
71374         if (column) {
71375             if (display == outside) {
71376                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
71377                     display = insideEnd;
71378                 }
71379             } else {
71380                 if (height + offsetY > attr.height) {
71381                     display = outside;
71382                     label.isOutside = true;
71383                 }
71384             }
71385             x = attr.x + groupBarWidth / 2;
71386             y = display == insideStart ?
71387                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
71388                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
71389                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
71390         }
71391         else {
71392             if (display == outside) {
71393                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
71394                     display = insideEnd;
71395                 }
71396             }
71397             else {
71398                 if (width + offsetX > attr.width) {
71399                     display = outside;
71400                     label.isOutside = true;
71401                 }
71402             }
71403             x = display == insideStart ?
71404                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
71405                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
71406                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
71407             y = attr.y + groupBarWidth / 2;
71408         }
71409         //set position
71410         finalAttr = {
71411             x: x,
71412             y: y
71413         };
71414         //rotate
71415         if (rotate) {
71416             finalAttr.rotate = {
71417                 x: x,
71418                 y: y,
71419                 degrees: 270
71420             };
71421         }
71422         //check for resizing
71423         if (animate && resizing) {
71424             if (column) {
71425                 x = attr.x + attr.width / 2;
71426                 y = zero;
71427             } else {
71428                 x = zero;
71429                 y = attr.y + attr.height / 2;
71430             }
71431             label.setAttributes({
71432                 x: x,
71433                 y: y
71434             }, true);
71435             if (rotate) {
71436                 label.setAttributes({
71437                     rotate: {
71438                         x: x,
71439                         y: y,
71440                         degrees: 270
71441                     }
71442                 }, true);
71443             }
71444         }
71445         //handle animation
71446         if (animate) {
71447             me.onAnimate(label, { to: finalAttr });
71448         }
71449         else {
71450             label.setAttributes(Ext.apply(finalAttr, {
71451                 hidden: false
71452             }), true);
71453         }
71454     },
71455
71456     /* @private
71457      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
71458      * changing visible sprites.
71459      * @param value
71460      */
71461     getLabelSize: function(value) {
71462         var tester = this.testerLabel,
71463             config = this.label,
71464             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
71465             rotated = config.orientation === 'vertical',
71466             bbox, w, h,
71467             undef;
71468         if (!tester) {
71469             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
71470                 type: 'text',
71471                 opacity: 0
71472             }, endLabelStyle));
71473         }
71474         tester.setAttributes({
71475             text: value
71476         }, true);
71477
71478         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
71479         bbox = tester.getBBox();
71480         w = bbox.width;
71481         h = bbox.height;
71482         return {
71483             width: rotated ? h : w,
71484             height: rotated ? w : h
71485         };
71486     },
71487
71488     // @private used to animate label, markers and other sprites.
71489     onAnimate: function(sprite, attr) {
71490         sprite.show();
71491         return this.callParent(arguments);
71492     },
71493
71494     isItemInPoint: function(x, y, item) {
71495         var bbox = item.sprite.getBBox();
71496         return bbox.x <= x && bbox.y <= y
71497             && (bbox.x + bbox.width) >= x
71498             && (bbox.y + bbox.height) >= y;
71499     },
71500
71501     // @private hide all markers
71502     hideAll: function() {
71503         var axes = this.chart.axes;
71504         if (!isNaN(this._index)) {
71505             if (!this.__excludes) {
71506                 this.__excludes = [];
71507             }
71508             this.__excludes[this._index] = true;
71509             this.drawSeries();
71510             axes.each(function(axis) {
71511                 axis.drawAxis();
71512             });
71513         }
71514     },
71515
71516     // @private show all markers
71517     showAll: function() {
71518         var axes = this.chart.axes;
71519         if (!isNaN(this._index)) {
71520             if (!this.__excludes) {
71521                 this.__excludes = [];
71522             }
71523             this.__excludes[this._index] = false;
71524             this.drawSeries();
71525             axes.each(function(axis) {
71526                 axis.drawAxis();
71527             });
71528         }
71529     },
71530
71531     /**
71532      * Returns a string with the color to be used for the series legend item.
71533      * @param index
71534      */
71535     getLegendColor: function(index) {
71536         var me = this,
71537             colorLength = me.colorArrayStyle.length;
71538
71539         if (me.style && me.style.fill) {
71540             return me.style.fill;
71541         } else {
71542             return me.colorArrayStyle[index % colorLength];
71543         }
71544     },
71545
71546     highlightItem: function(item) {
71547         this.callParent(arguments);
71548         this.renderLabels();
71549     },
71550
71551     unHighlightItem: function() {
71552         this.callParent(arguments);
71553         this.renderLabels();
71554     },
71555
71556     cleanHighlights: function() {
71557         this.callParent(arguments);
71558         this.renderLabels();
71559     }
71560 });
71561 /**
71562  * @class Ext.chart.series.Column
71563  * @extends Ext.chart.series.Bar
71564  *
71565  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
71566  * visualization technique to display quantitative information for different categories that can
71567  * show some progression (or regression) in the data set. As with all other series, the Column Series
71568  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
71569  * information. A typical configuration object for the column series could be:
71570  *
71571  *     @example
71572  *     var store = Ext.create('Ext.data.JsonStore', {
71573  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
71574  *         data: [
71575  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
71576  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
71577  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
71578  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
71579  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
71580  *         ]
71581  *     });
71582  *
71583  *     Ext.create('Ext.chart.Chart', {
71584  *         renderTo: Ext.getBody(),
71585  *         width: 500,
71586  *         height: 300,
71587  *         animate: true,
71588  *         store: store,
71589  *         axes: [
71590  *             {
71591  *                 type: 'Numeric',
71592  *                 position: 'left',
71593  *                 fields: ['data1'],
71594  *                 label: {
71595  *                     renderer: Ext.util.Format.numberRenderer('0,0')
71596  *                 },
71597  *                 title: 'Sample Values',
71598  *                 grid: true,
71599  *                 minimum: 0
71600  *             },
71601  *             {
71602  *                 type: 'Category',
71603  *                 position: 'bottom',
71604  *                 fields: ['name'],
71605  *                 title: 'Sample Metrics'
71606  *             }
71607  *         ],
71608  *         series: [
71609  *             {
71610  *                 type: 'column',
71611  *                 axis: 'left',
71612  *                 highlight: true,
71613  *                 tips: {
71614  *                   trackMouse: true,
71615  *                   width: 140,
71616  *                   height: 28,
71617  *                   renderer: function(storeItem, item) {
71618  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
71619  *                   }
71620  *                 },
71621  *                 label: {
71622  *                   display: 'insideEnd',
71623  *                   'text-anchor': 'middle',
71624  *                     field: 'data1',
71625  *                     renderer: Ext.util.Format.numberRenderer('0'),
71626  *                     orientation: 'vertical',
71627  *                     color: '#333'
71628  *                 },
71629  *                 xField: 'name',
71630  *                 yField: 'data1'
71631  *             }
71632  *         ]
71633  *     });
71634  *
71635  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
71636  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
71637  * field to the data store `name` property and the `yField` as the data1 property of a store element.
71638  */
71639 Ext.define('Ext.chart.series.Column', {
71640
71641     /* Begin Definitions */
71642
71643     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
71644
71645     extend: 'Ext.chart.series.Bar',
71646
71647     /* End Definitions */
71648
71649     type: 'column',
71650     alias: 'series.column',
71651
71652     column: true,
71653
71654     /**
71655      * @cfg {Number} xPadding
71656      * Padding between the left/right axes and the bars
71657      */
71658     xPadding: 10,
71659
71660     /**
71661      * @cfg {Number} yPadding
71662      * Padding between the top/bottom axes and the bars
71663      */
71664     yPadding: 0
71665 });
71666 /**
71667  * @class Ext.chart.series.Gauge
71668  * @extends Ext.chart.series.Series
71669  * 
71670  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
71671  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
71672  * visualization and using the `setValue` method to adjust the value you want.
71673  *
71674  * A chart/series configuration for the Gauge visualization could look like this:
71675  * 
71676  *     {
71677  *         xtype: 'chart',
71678  *         store: store,
71679  *         axes: [{
71680  *             type: 'gauge',
71681  *             position: 'gauge',
71682  *             minimum: 0,
71683  *             maximum: 100,
71684  *             steps: 10,
71685  *             margin: -10
71686  *         }],
71687  *         series: [{
71688  *             type: 'gauge',
71689  *             field: 'data1',
71690  *             donut: false,
71691  *             colorSet: ['#F49D10', '#ddd']
71692  *         }]
71693  *     }
71694  * 
71695  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
71696  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
71697  * the visual display and the color set to be used with the visualization.
71698  * 
71699  * @xtype gauge
71700  */
71701 Ext.define('Ext.chart.series.Gauge', {
71702
71703     /* Begin Definitions */
71704
71705     extend: 'Ext.chart.series.Series',
71706
71707     /* End Definitions */
71708
71709     type: "gauge",
71710     alias: 'series.gauge',
71711
71712     rad: Math.PI / 180,
71713
71714     /**
71715      * @cfg {Number} highlightDuration
71716      * The duration for the pie slice highlight effect.
71717      */
71718     highlightDuration: 150,
71719
71720     /**
71721      * @cfg {String} angleField (required)
71722      * The store record field name to be used for the pie angles.
71723      * The values bound to this field name must be positive real numbers.
71724      */
71725     angleField: false,
71726
71727     /**
71728      * @cfg {Boolean} needle
71729      * Use the Gauge Series as an area series or add a needle to it. Default's false.
71730      */
71731     needle: false,
71732     
71733     /**
71734      * @cfg {Boolean/Number} donut
71735      * Use the entire disk or just a fraction of it for the gauge. Default's false.
71736      */
71737     donut: false,
71738
71739     /**
71740      * @cfg {Boolean} showInLegend
71741      * Whether to add the pie chart elements as legend items. Default's false.
71742      */
71743     showInLegend: false,
71744
71745     /**
71746      * @cfg {Object} style
71747      * An object containing styles for overriding series styles from Theming.
71748      */
71749     style: {},
71750     
71751     constructor: function(config) {
71752         this.callParent(arguments);
71753         var me = this,
71754             chart = me.chart,
71755             surface = chart.surface,
71756             store = chart.store,
71757             shadow = chart.shadow, i, l, cfg;
71758         Ext.apply(me, config, {
71759             shadowAttributes: [{
71760                 "stroke-width": 6,
71761                 "stroke-opacity": 1,
71762                 stroke: 'rgb(200, 200, 200)',
71763                 translate: {
71764                     x: 1.2,
71765                     y: 2
71766                 }
71767             },
71768             {
71769                 "stroke-width": 4,
71770                 "stroke-opacity": 1,
71771                 stroke: 'rgb(150, 150, 150)',
71772                 translate: {
71773                     x: 0.9,
71774                     y: 1.5
71775                 }
71776             },
71777             {
71778                 "stroke-width": 2,
71779                 "stroke-opacity": 1,
71780                 stroke: 'rgb(100, 100, 100)',
71781                 translate: {
71782                     x: 0.6,
71783                     y: 1
71784                 }
71785             }]
71786         });
71787         me.group = surface.getGroup(me.seriesId);
71788         if (shadow) {
71789             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
71790                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
71791             }
71792         }
71793         surface.customAttributes.segment = function(opt) {
71794             return me.getSegment(opt);
71795         };
71796     },
71797     
71798     //@private updates some onbefore render parameters.
71799     initialize: function() {
71800         var me = this,
71801             store = me.chart.getChartStore();
71802         //Add yFields to be used in Legend.js
71803         me.yField = [];
71804         if (me.label.field) {
71805             store.each(function(rec) {
71806                 me.yField.push(rec.get(me.label.field));
71807             });
71808         }
71809     },
71810
71811     // @private returns an object with properties for a Slice
71812     getSegment: function(opt) {
71813         var me = this,
71814             rad = me.rad,
71815             cos = Math.cos,
71816             sin = Math.sin,
71817             abs = Math.abs,
71818             x = me.centerX,
71819             y = me.centerY,
71820             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
71821             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
71822             delta = 1e-2,
71823             r = opt.endRho - opt.startRho,
71824             startAngle = opt.startAngle,
71825             endAngle = opt.endAngle,
71826             midAngle = (startAngle + endAngle) / 2 * rad,
71827             margin = opt.margin || 0,
71828             flag = abs(endAngle - startAngle) > 180,
71829             a1 = Math.min(startAngle, endAngle) * rad,
71830             a2 = Math.max(startAngle, endAngle) * rad,
71831             singleSlice = false;
71832
71833         x += margin * cos(midAngle);
71834         y += margin * sin(midAngle);
71835
71836         x1 = x + opt.startRho * cos(a1);
71837         y1 = y + opt.startRho * sin(a1);
71838
71839         x2 = x + opt.endRho * cos(a1);
71840         y2 = y + opt.endRho * sin(a1);
71841
71842         x3 = x + opt.startRho * cos(a2);
71843         y3 = y + opt.startRho * sin(a2);
71844
71845         x4 = x + opt.endRho * cos(a2);
71846         y4 = y + opt.endRho * sin(a2);
71847
71848         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
71849             singleSlice = true;
71850         }
71851         //Solves mysterious clipping bug with IE
71852         if (singleSlice) {
71853             return {
71854                 path: [
71855                 ["M", x1, y1],
71856                 ["L", x2, y2],
71857                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71858                 ["Z"]]
71859             };
71860         } else {
71861             return {
71862                 path: [
71863                 ["M", x1, y1],
71864                 ["L", x2, y2],
71865                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71866                 ["L", x3, y3],
71867                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
71868                 ["Z"]]
71869             };
71870         }
71871     },
71872
71873     // @private utility function to calculate the middle point of a pie slice.
71874     calcMiddle: function(item) {
71875         var me = this,
71876             rad = me.rad,
71877             slice = item.slice,
71878             x = me.centerX,
71879             y = me.centerY,
71880             startAngle = slice.startAngle,
71881             endAngle = slice.endAngle,
71882             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
71883             donut = +me.donut,
71884             a1 = Math.min(startAngle, endAngle) * rad,
71885             a2 = Math.max(startAngle, endAngle) * rad,
71886             midAngle = -(a1 + (a2 - a1) / 2),
71887             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
71888             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
71889
71890         item.middle = {
71891             x: xm,
71892             y: ym
71893         };
71894     },
71895
71896     /**
71897      * Draws the series for the current chart.
71898      */
71899     drawSeries: function() {
71900         var me = this,
71901             chart = me.chart,
71902             store = chart.getChartStore(),
71903             group = me.group,
71904             animate = me.chart.animate,
71905             axis = me.chart.axes.get(0),
71906             minimum = axis && axis.minimum || me.minimum || 0,
71907             maximum = axis && axis.maximum || me.maximum || 0,
71908             field = me.angleField || me.field || me.xField,
71909             surface = chart.surface,
71910             chartBBox = chart.chartBBox,
71911             rad = me.rad,
71912             donut = +me.donut,
71913             values = {},
71914             items = [],
71915             seriesStyle = me.seriesStyle,
71916             seriesLabelStyle = me.seriesLabelStyle,
71917             colorArrayStyle = me.colorArrayStyle,
71918             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
71919             gutterX = chart.maxGutter[0],
71920             gutterY = chart.maxGutter[1],
71921             cos = Math.cos,
71922             sin = Math.sin,
71923             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
71924             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
71925             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
71926         
71927         Ext.apply(seriesStyle, me.style || {});
71928
71929         me.setBBox();
71930         bbox = me.bbox;
71931
71932         //override theme colors
71933         if (me.colorSet) {
71934             colorArrayStyle = me.colorSet;
71935             colorArrayLength = colorArrayStyle.length;
71936         }
71937         
71938         //if not store or store is empty then there's nothing to draw
71939         if (!store || !store.getCount()) {
71940             return;
71941         }
71942         
71943         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
71944         centerY = me.centerY = chartBBox.y + chartBBox.height;
71945         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
71946         me.slices = slices = [];
71947         me.items = items = [];
71948         
71949         if (!me.value) {
71950             record = store.getAt(0);
71951             me.value = record.get(field);
71952         }
71953         
71954         value = me.value;
71955         if (me.needle) {
71956             sliceA = {
71957                 series: me,
71958                 value: value,
71959                 startAngle: -180,
71960                 endAngle: 0,
71961                 rho: me.radius
71962             };
71963             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71964             slices.push(sliceA);
71965         } else {
71966             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71967             sliceA = {
71968                 series: me,
71969                 value: value,
71970                 startAngle: -180,
71971                 endAngle: splitAngle,
71972                 rho: me.radius
71973             };
71974             sliceB = {
71975                 series: me,
71976                 value: me.maximum - value,
71977                 startAngle: splitAngle,
71978                 endAngle: 0,
71979                 rho: me.radius
71980             };
71981             slices.push(sliceA, sliceB);
71982         }
71983         
71984         //do pie slices after.
71985         for (i = 0, ln = slices.length; i < ln; i++) {
71986             slice = slices[i];
71987             sprite = group.getAt(i);
71988             //set pie slice properties
71989             rendererAttributes = Ext.apply({
71990                 segment: {
71991                     startAngle: slice.startAngle,
71992                     endAngle: slice.endAngle,
71993                     margin: 0,
71994                     rho: slice.rho,
71995                     startRho: slice.rho * +donut / 100,
71996                     endRho: slice.rho
71997                 } 
71998             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
71999
72000             item = Ext.apply({},
72001             rendererAttributes.segment, {
72002                 slice: slice,
72003                 series: me,
72004                 storeItem: record,
72005                 index: i
72006             });
72007             items[i] = item;
72008             // Create a new sprite if needed (no height)
72009             if (!sprite) {
72010                 spriteOptions = Ext.apply({
72011                     type: "path",
72012                     group: group
72013                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72014                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
72015             }
72016             slice.sprite = slice.sprite || [];
72017             item.sprite = sprite;
72018             slice.sprite.push(sprite);
72019             if (animate) {
72020                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
72021                 sprite._to = rendererAttributes;
72022                 me.onAnimate(sprite, {
72023                     to: rendererAttributes
72024                 });
72025             } else {
72026                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
72027                     hidden: false
72028                 }), i, store);
72029                 sprite.setAttributes(rendererAttributes, true);
72030             }
72031         }
72032         
72033         if (me.needle) {
72034             splitAngle = splitAngle * Math.PI / 180;
72035             
72036             if (!me.needleSprite) {
72037                 me.needleSprite = me.chart.surface.add({
72038                     type: 'path',
72039                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72040                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72041                            'L', centerX + me.radius * cos(splitAngle),
72042                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
72043                     'stroke-width': 4,
72044                     'stroke': '#222'
72045                 });
72046             } else {
72047                 if (animate) {
72048                     me.onAnimate(me.needleSprite, {
72049                         to: {
72050                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72051                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72052                                'L', centerX + me.radius * cos(splitAngle),
72053                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72054                         }
72055                     });
72056                 } else {
72057                     me.needleSprite.setAttributes({
72058                         type: 'path',
72059                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72060                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72061                                'L', centerX + me.radius * cos(splitAngle),
72062                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72063                     });
72064                 }
72065             }
72066             me.needleSprite.setAttributes({
72067                 hidden: false    
72068             }, true);
72069         }
72070         
72071         delete me.value;
72072     },
72073     
72074     /**
72075      * Sets the Gauge chart to the current specified value.
72076     */
72077     setValue: function (value) {
72078         this.value = value;
72079         this.drawSeries();
72080     },
72081
72082     // @private callback for when creating a label sprite.
72083     onCreateLabel: function(storeItem, item, i, display) {},
72084
72085     // @private callback for when placing a label sprite.
72086     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
72087
72088     // @private callback for when placing a callout.
72089     onPlaceCallout: function() {},
72090
72091     // @private handles sprite animation for the series.
72092     onAnimate: function(sprite, attr) {
72093         sprite.show();
72094         return this.callParent(arguments);
72095     },
72096
72097     isItemInPoint: function(x, y, item, i) {
72098         return false;
72099     },
72100     
72101     // @private shows all elements in the series.
72102     showAll: function() {
72103         if (!isNaN(this._index)) {
72104             this.__excludes[this._index] = false;
72105             this.drawSeries();
72106         }
72107     },
72108     
72109     /**
72110      * Returns the color of the series (to be displayed as color for the series legend item).
72111      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
72112      */
72113     getLegendColor: function(index) {
72114         var me = this;
72115         return me.colorArrayStyle[index % me.colorArrayStyle.length];
72116     }
72117 });
72118
72119
72120 /**
72121  * @class Ext.chart.series.Line
72122  * @extends Ext.chart.series.Cartesian
72123  *
72124  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
72125  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
72126  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
72127  * documentation for more information. A typical configuration object for the line series could be:
72128  *
72129  *     @example
72130  *     var store = Ext.create('Ext.data.JsonStore', {
72131  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72132  *         data: [
72133  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72134  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72135  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72136  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72137  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
72138  *         ]
72139  *     });
72140  *
72141  *     Ext.create('Ext.chart.Chart', {
72142  *         renderTo: Ext.getBody(),
72143  *         width: 500,
72144  *         height: 300,
72145  *         animate: true,
72146  *         store: store,
72147  *         axes: [
72148  *             {
72149  *                 type: 'Numeric',
72150  *                 position: 'left',
72151  *                 fields: ['data1', 'data2'],
72152  *                 label: {
72153  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72154  *                 },
72155  *                 title: 'Sample Values',
72156  *                 grid: true,
72157  *                 minimum: 0
72158  *             },
72159  *             {
72160  *                 type: 'Category',
72161  *                 position: 'bottom',
72162  *                 fields: ['name'],
72163  *                 title: 'Sample Metrics'
72164  *             }
72165  *         ],
72166  *         series: [
72167  *             {
72168  *                 type: 'line',
72169  *                 highlight: {
72170  *                     size: 7,
72171  *                     radius: 7
72172  *                 },
72173  *                 axis: 'left',
72174  *                 xField: 'name',
72175  *                 yField: 'data1',
72176  *                 markerConfig: {
72177  *                     type: 'cross',
72178  *                     size: 4,
72179  *                     radius: 4,
72180  *                     'stroke-width': 0
72181  *                 }
72182  *             },
72183  *             {
72184  *                 type: 'line',
72185  *                 highlight: {
72186  *                     size: 7,
72187  *                     radius: 7
72188  *                 },
72189  *                 axis: 'left',
72190  *                 fill: true,
72191  *                 xField: 'name',
72192  *                 yField: 'data2',
72193  *                 markerConfig: {
72194  *                     type: 'circle',
72195  *                     size: 4,
72196  *                     radius: 4,
72197  *                     'stroke-width': 0
72198  *                 }
72199  *             }
72200  *         ]
72201  *     });
72202  *
72203  * In this configuration we're adding two series (or lines), one bound to the `data1`
72204  * property of the store and the other to `data3`. The type for both configurations is
72205  * `line`. The `xField` for both series is the same, the name propert of the store.
72206  * Both line series share the same axis, the left axis. You can set particular marker
72207  * configuration by adding properties onto the markerConfig object. Both series have
72208  * an object as highlight so that markers animate smoothly to the properties in highlight
72209  * when hovered. The second series has `fill=true` which means that the line will also
72210  * have an area below it of the same color.
72211  *
72212  * **Note:** In the series definition remember to explicitly set the axis to bind the
72213  * values of the line series to. This can be done by using the `axis` configuration property.
72214  */
72215 Ext.define('Ext.chart.series.Line', {
72216
72217     /* Begin Definitions */
72218
72219     extend: 'Ext.chart.series.Cartesian',
72220
72221     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
72222
72223     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
72224
72225     /* End Definitions */
72226
72227     type: 'line',
72228
72229     alias: 'series.line',
72230
72231     /**
72232      * @cfg {String} axis
72233      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
72234      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
72235      * relative scale will be used.
72236      */
72237
72238     /**
72239      * @cfg {Number} selectionTolerance
72240      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
72241      */
72242     selectionTolerance: 20,
72243
72244     /**
72245      * @cfg {Boolean} showMarkers
72246      * Whether markers should be displayed at the data points along the line. If true,
72247      * then the {@link #markerConfig} config item will determine the markers' styling.
72248      */
72249     showMarkers: true,
72250
72251     /**
72252      * @cfg {Object} markerConfig
72253      * The display style for the markers. Only used if {@link #showMarkers} is true.
72254      * The markerConfig is a configuration object containing the same set of properties defined in
72255      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
72256      * pass the object:
72257      *
72258      <pre><code>
72259         markerConfig: {
72260             type: 'circle',
72261             radius: 4,
72262             'fill': '#f00'
72263         }
72264      </code></pre>
72265
72266      */
72267     markerConfig: {},
72268
72269     /**
72270      * @cfg {Object} style
72271      * An object containing style properties for the visualization lines and fill.
72272      * These styles will override the theme styles.  The following are valid style properties:
72273      *
72274      * - `stroke` - an rgb or hex color string for the background color of the line
72275      * - `stroke-width` - the width of the stroke (integer)
72276      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
72277      * - `opacity` - the opacity of the line and the fill color (decimal)
72278      *
72279      * Example usage:
72280      *
72281      *     style: {
72282      *         stroke: '#00ff00',
72283      *         'stroke-width': 10,
72284      *         fill: '#80A080',
72285      *         opacity: 0.2
72286      *     }
72287      */
72288     style: {},
72289
72290     /**
72291      * @cfg {Boolean/Number} smooth
72292      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
72293      * straight line segments will be drawn.
72294      *
72295      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
72296      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
72297      *
72298      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
72299      */
72300     smooth: false,
72301
72302     /**
72303      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
72304      */
72305     defaultSmoothness: 3,
72306
72307     /**
72308      * @cfg {Boolean} fill
72309      * If true, the area below the line will be filled in using the {@link #style eefill} and
72310      * {@link #style opacity} config properties. Defaults to false.
72311      */
72312     fill: false,
72313
72314     constructor: function(config) {
72315         this.callParent(arguments);
72316         var me = this,
72317             surface = me.chart.surface,
72318             shadow = me.chart.shadow,
72319             i, l;
72320         Ext.apply(me, config, {
72321             highlightCfg: {
72322                 'stroke-width': 3
72323             },
72324             shadowAttributes: [{
72325                 "stroke-width": 6,
72326                 "stroke-opacity": 0.05,
72327                 stroke: 'rgb(0, 0, 0)',
72328                 translate: {
72329                     x: 1,
72330                     y: 1
72331                 }
72332             }, {
72333                 "stroke-width": 4,
72334                 "stroke-opacity": 0.1,
72335                 stroke: 'rgb(0, 0, 0)',
72336                 translate: {
72337                     x: 1,
72338                     y: 1
72339                 }
72340             }, {
72341                 "stroke-width": 2,
72342                 "stroke-opacity": 0.15,
72343                 stroke: 'rgb(0, 0, 0)',
72344                 translate: {
72345                     x: 1,
72346                     y: 1
72347                 }
72348             }]
72349         });
72350         me.group = surface.getGroup(me.seriesId);
72351         if (me.showMarkers) {
72352             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
72353         }
72354         if (shadow) {
72355             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
72356                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
72357             }
72358         }
72359     },
72360
72361     // @private makes an average of points when there are more data points than pixels to be rendered.
72362     shrink: function(xValues, yValues, size) {
72363         // Start at the 2nd point...
72364         var len = xValues.length,
72365             ratio = Math.floor(len / size),
72366             i = 1,
72367             xSum = 0,
72368             ySum = 0,
72369             xRes = [xValues[0]],
72370             yRes = [yValues[0]];
72371
72372         for (; i < len; ++i) {
72373             xSum += xValues[i] || 0;
72374             ySum += yValues[i] || 0;
72375             if (i % ratio == 0) {
72376                 xRes.push(xSum/ratio);
72377                 yRes.push(ySum/ratio);
72378                 xSum = 0;
72379                 ySum = 0;
72380             }
72381         }
72382         return {
72383             x: xRes,
72384             y: yRes
72385         };
72386     },
72387
72388     /**
72389      * Draws the series for the current chart.
72390      */
72391     drawSeries: function() {
72392         var me = this,
72393             chart = me.chart,
72394             chartAxes = chart.axes,
72395             store = chart.getChartStore(),
72396             storeCount = store.getCount(),
72397             surface = me.chart.surface,
72398             bbox = {},
72399             group = me.group,
72400             showMarkers = me.showMarkers,
72401             markerGroup = me.markerGroup,
72402             enableShadows = chart.shadow,
72403             shadowGroups = me.shadowGroups,
72404             shadowAttributes = me.shadowAttributes,
72405             smooth = me.smooth,
72406             lnsh = shadowGroups.length,
72407             dummyPath = ["M"],
72408             path = ["M"],
72409             renderPath = ["M"],
72410             smoothPath = ["M"],
72411             markerIndex = chart.markerIndex,
72412             axes = [].concat(me.axis),
72413             shadowBarAttr,
72414             xValues = [],
72415             xValueMap = {},
72416             yValues = [],
72417             yValueMap = {},
72418             onbreak = false,
72419             storeIndices = [],
72420             markerStyle = me.markerStyle,
72421             seriesStyle = me.style,
72422             colorArrayStyle = me.colorArrayStyle,
72423             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
72424             isNumber = Ext.isNumber,
72425             seriesIdx = me.seriesIdx, 
72426             boundAxes = me.getAxesForXAndYFields(),
72427             boundXAxis = boundAxes.xAxis,
72428             boundYAxis = boundAxes.yAxis,
72429             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
72430             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
72431             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
72432             endLineStyle, type, count, items;
72433
72434         if (me.fireEvent('beforedraw', me) === false) {
72435             return;
72436         }
72437
72438         //if store is empty or the series is excluded in the legend then there's nothing to draw.
72439         if (!storeCount || me.seriesIsHidden) {
72440             items = this.items;
72441             if (items) {
72442                 for (i = 0, ln = items.length; i < ln; ++i) {
72443                     if (items[i].sprite) {
72444                         items[i].sprite.hide(true);
72445                     }
72446                 }
72447             }
72448             return;
72449         }
72450
72451         //prepare style objects for line and markers
72452         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
72453         type = endMarkerStyle.type;
72454         delete endMarkerStyle.type;
72455         endLineStyle = seriesStyle;
72456         //if no stroke with is specified force it to 0.5 because this is
72457         //about making *lines*
72458         if (!endLineStyle['stroke-width']) {
72459             endLineStyle['stroke-width'] = 0.5;
72460         }
72461         //If we're using a time axis and we need to translate the points,
72462         //then reuse the first markers as the last markers.
72463         if (markerIndex && markerGroup && markerGroup.getCount()) {
72464             for (i = 0; i < markerIndex; i++) {
72465                 marker = markerGroup.getAt(i);
72466                 markerGroup.remove(marker);
72467                 markerGroup.add(marker);
72468                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
72469                 marker.setAttributes({
72470                     x: 0,
72471                     y: 0,
72472                     translate: {
72473                         x: markerAux.attr.translation.x,
72474                         y: markerAux.attr.translation.y
72475                     }
72476                 }, true);
72477             }
72478         }
72479
72480         me.unHighlightItem();
72481         me.cleanHighlights();
72482
72483         me.setBBox();
72484         bbox = me.bbox;
72485         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
72486         for (i = 0, ln = axes.length; i < ln; i++) {
72487             axis = chartAxes.get(axes[i]);
72488             if (axis) {
72489                 ends = axis.calcEnds();
72490                 if (axis.position == 'top' || axis.position == 'bottom') {
72491                     minX = ends.from;
72492                     maxX = ends.to;
72493                 }
72494                 else {
72495                     minY = ends.from;
72496                     maxY = ends.to;
72497                 }
72498             }
72499         }
72500         // If a field was specified without a corresponding axis, create one to get bounds
72501         //only do this for the axis where real values are bound (that's why we check for
72502         //me.axis)
72503         if (me.xField && !isNumber(minX) &&
72504             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
72505             !chartAxes.get(boundXAxis)) {
72506             axis = Ext.create('Ext.chart.axis.Axis', {
72507                 chart: chart,
72508                 fields: [].concat(me.xField)
72509             }).calcEnds();
72510             minX = axis.from;
72511             maxX = axis.to;
72512         }
72513         if (me.yField && !isNumber(minY) &&
72514             (boundYAxis == 'right' || boundYAxis == 'left') &&
72515             !chartAxes.get(boundYAxis)) {
72516             axis = Ext.create('Ext.chart.axis.Axis', {
72517                 chart: chart,
72518                 fields: [].concat(me.yField)
72519             }).calcEnds();
72520             minY = axis.from;
72521             maxY = axis.to;
72522         }
72523         if (isNaN(minX)) {
72524             minX = 0;
72525             xScale = bbox.width / ((storeCount - 1) || 1);
72526         }
72527         else {
72528             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
72529         }
72530
72531         if (isNaN(minY)) {
72532             minY = 0;
72533             yScale = bbox.height / ((storeCount - 1) || 1);
72534         }
72535         else {
72536             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
72537         }
72538
72539         // Extract all x and y values from the store
72540         me.eachRecord(function(record, i) {
72541             xValue = record.get(me.xField);
72542
72543             // Ensure a value
72544             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
72545                 //set as uniform distribution if the axis is a category axis.
72546                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
72547                     if (xValue in xValueMap) {
72548                         xValue = xValueMap[xValue];
72549                     } else {
72550                         xValue = xValueMap[xValue] = i;
72551                     }
72552             }
72553
72554             // Filter out values that don't fit within the pan/zoom buffer area
72555             yValue = record.get(me.yField);
72556             //skip undefined values
72557             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
72558                 return;
72559             }
72560             // Ensure a value
72561             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
72562                 //set as uniform distribution if the axis is a category axis.
72563                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
72564                 yValue = i;
72565             }
72566             storeIndices.push(i);
72567             xValues.push(xValue);
72568             yValues.push(yValue);
72569         });
72570
72571         ln = xValues.length;
72572         if (ln > bbox.width) {
72573             coords = me.shrink(xValues, yValues, bbox.width);
72574             xValues = coords.x;
72575             yValues = coords.y;
72576         }
72577
72578         me.items = [];
72579
72580         count = 0;
72581         ln = xValues.length;
72582         for (i = 0; i < ln; i++) {
72583             xValue = xValues[i];
72584             yValue = yValues[i];
72585             if (yValue === false) {
72586                 if (path.length == 1) {
72587                     path = [];
72588                 }
72589                 onbreak = true;
72590                 me.items.push(false);
72591                 continue;
72592             } else {
72593                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
72594                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
72595                 if (onbreak) {
72596                     onbreak = false;
72597                     path.push('M');
72598                 }
72599                 path = path.concat([x, y]);
72600             }
72601             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
72602                 firstY = y;
72603                 firstX = x;
72604             }
72605             // If this is the first line, create a dummypath to animate in from.
72606             if (!me.line || chart.resizing) {
72607                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
72608             }
72609
72610             // When resizing, reset before animating
72611             if (chart.animate && chart.resizing && me.line) {
72612                 me.line.setAttributes({
72613                     path: dummyPath
72614                 }, true);
72615                 if (me.fillPath) {
72616                     me.fillPath.setAttributes({
72617                         path: dummyPath,
72618                         opacity: 0.2
72619                     }, true);
72620                 }
72621                 if (me.line.shadows) {
72622                     shadows = me.line.shadows;
72623                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
72624                         shadow = shadows[j];
72625                         shadow.setAttributes({
72626                             path: dummyPath
72627                         }, true);
72628                     }
72629                 }
72630             }
72631             if (showMarkers) {
72632                 marker = markerGroup.getAt(count++);
72633                 if (!marker) {
72634                     marker = Ext.chart.Shape[type](surface, Ext.apply({
72635                         group: [group, markerGroup],
72636                         x: 0, y: 0,
72637                         translate: {
72638                             x: +(prevX || x),
72639                             y: prevY || (bbox.y + bbox.height / 2)
72640                         },
72641                         value: '"' + xValue + ', ' + yValue + '"',
72642                         zIndex: 4000
72643                     }, endMarkerStyle));
72644                     marker._to = {
72645                         translate: {
72646                             x: +x,
72647                             y: +y
72648                         }
72649                     };
72650                 } else {
72651                     marker.setAttributes({
72652                         value: '"' + xValue + ', ' + yValue + '"',
72653                         x: 0, y: 0,
72654                         hidden: false
72655                     }, true);
72656                     marker._to = {
72657                         translate: {
72658                             x: +x, 
72659                             y: +y
72660                         }
72661                     };
72662                 }
72663             }
72664             me.items.push({
72665                 series: me,
72666                 value: [xValue, yValue],
72667                 point: [x, y],
72668                 sprite: marker,
72669                 storeItem: store.getAt(storeIndices[i])
72670             });
72671             prevX = x;
72672             prevY = y;
72673         }
72674
72675         if (path.length <= 1) {
72676             //nothing to be rendered
72677             return;
72678         }
72679
72680         if (me.smooth) {
72681             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
72682         }
72683
72684         renderPath = smooth ? smoothPath : path;
72685
72686         //Correct path if we're animating timeAxis intervals
72687         if (chart.markerIndex && me.previousPath) {
72688             fromPath = me.previousPath;
72689             if (!smooth) {
72690                 Ext.Array.erase(fromPath, 1, 2);
72691             }
72692         } else {
72693             fromPath = path;
72694         }
72695
72696         // Only create a line if one doesn't exist.
72697         if (!me.line) {
72698             me.line = surface.add(Ext.apply({
72699                 type: 'path',
72700                 group: group,
72701                 path: dummyPath,
72702                 stroke: endLineStyle.stroke || endLineStyle.fill
72703             }, endLineStyle || {}));
72704
72705             if (enableShadows) {
72706                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
72707             }
72708
72709             //unset fill here (there's always a default fill withing the themes).
72710             me.line.setAttributes({
72711                 fill: 'none',
72712                 zIndex: 3000
72713             });
72714             if (!endLineStyle.stroke && colorArrayLength) {
72715                 me.line.setAttributes({
72716                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
72717                 }, true);
72718             }
72719             if (enableShadows) {
72720                 //create shadows
72721                 shadows = me.line.shadows = [];
72722                 for (shindex = 0; shindex < lnsh; shindex++) {
72723                     shadowBarAttr = shadowAttributes[shindex];
72724                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
72725                     shadow = surface.add(Ext.apply({}, {
72726                         type: 'path',
72727                         group: shadowGroups[shindex]
72728                     }, shadowBarAttr));
72729                     shadows.push(shadow);
72730                 }
72731             }
72732         }
72733         if (me.fill) {
72734             fillPath = renderPath.concat([
72735                 ["L", x, bbox.y + bbox.height],
72736                 ["L", firstX, bbox.y + bbox.height],
72737                 ["L", firstX, firstY]
72738             ]);
72739             if (!me.fillPath) {
72740                 me.fillPath = surface.add({
72741                     group: group,
72742                     type: 'path',
72743                     opacity: endLineStyle.opacity || 0.3,
72744                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72745                     path: dummyPath
72746                 });
72747             }
72748         }
72749         markerCount = showMarkers && markerGroup.getCount();
72750         if (chart.animate) {
72751             fill = me.fill;
72752             line = me.line;
72753             //Add renderer to line. There is not unique record associated with this.
72754             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
72755             Ext.apply(rendererAttributes, endLineStyle || {}, {
72756                 stroke: endLineStyle.stroke || endLineStyle.fill
72757             });
72758             //fill should not be used here but when drawing the special fill path object
72759             delete rendererAttributes.fill;
72760             line.show(true);
72761             if (chart.markerIndex && me.previousPath) {
72762                 me.animation = animation = me.onAnimate(line, {
72763                     to: rendererAttributes,
72764                     from: {
72765                         path: fromPath
72766                     }
72767                 });
72768             } else {
72769                 me.animation = animation = me.onAnimate(line, {
72770                     to: rendererAttributes
72771                 });
72772             }
72773             //animate shadows
72774             if (enableShadows) {
72775                 shadows = line.shadows;
72776                 for(j = 0; j < lnsh; j++) {
72777                     shadows[j].show(true);
72778                     if (chart.markerIndex && me.previousPath) {
72779                         me.onAnimate(shadows[j], {
72780                             to: { path: renderPath },
72781                             from: { path: fromPath }
72782                         });
72783                     } else {
72784                         me.onAnimate(shadows[j], {
72785                             to: { path: renderPath }
72786                         });
72787                     }
72788                 }
72789             }
72790             //animate fill path
72791             if (fill) {
72792                 me.fillPath.show(true);
72793                 me.onAnimate(me.fillPath, {
72794                     to: Ext.apply({}, {
72795                         path: fillPath,
72796                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72797                         'stroke-width': 0
72798                     }, endLineStyle || {})
72799                 });
72800             }
72801             //animate markers
72802             if (showMarkers) {
72803                 count = 0;
72804                 for(i = 0; i < ln; i++) {
72805                     if (me.items[i]) {
72806                         item = markerGroup.getAt(count++);
72807                         if (item) {
72808                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72809                             me.onAnimate(item, {
72810                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
72811                             });
72812                             item.show(true);
72813                         }
72814                     }
72815                 }
72816                 for(; count < markerCount; count++) {
72817                     item = markerGroup.getAt(count);
72818                     item.hide(true);
72819                 }
72820 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
72821 //                    item = markerGroup.getAt(i);
72822 //                    item.hide(true);
72823 //                }
72824             }
72825         } else {
72826             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
72827             Ext.apply(rendererAttributes, endLineStyle || {}, {
72828                 stroke: endLineStyle.stroke || endLineStyle.fill
72829             });
72830             //fill should not be used here but when drawing the special fill path object
72831             delete rendererAttributes.fill;
72832             me.line.setAttributes(rendererAttributes, true);
72833             //set path for shadows
72834             if (enableShadows) {
72835                 shadows = me.line.shadows;
72836                 for(j = 0; j < lnsh; j++) {
72837                     shadows[j].setAttributes({
72838                         path: renderPath,
72839                         hidden: false
72840                     }, true);
72841                 }
72842             }
72843             if (me.fill) {
72844                 me.fillPath.setAttributes({
72845                     path: fillPath,
72846                     hidden: false
72847                 }, true);
72848             }
72849             if (showMarkers) {
72850                 count = 0;
72851                 for(i = 0; i < ln; i++) {
72852                     if (me.items[i]) {
72853                         item = markerGroup.getAt(count++);
72854                         if (item) {
72855                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72856                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
72857                             item.show(true);
72858                         }
72859                     }
72860                 }
72861                 for(; count < markerCount; count++) {
72862                     item = markerGroup.getAt(count);
72863                     item.hide(true);
72864                 }
72865             }
72866         }
72867
72868         if (chart.markerIndex) {
72869             if (me.smooth) {
72870                 Ext.Array.erase(path, 1, 2);
72871             } else {
72872                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
72873             }
72874             me.previousPath = path;
72875         }
72876         me.renderLabels();
72877         me.renderCallouts();
72878
72879         me.fireEvent('draw', me);
72880     },
72881
72882     // @private called when a label is to be created.
72883     onCreateLabel: function(storeItem, item, i, display) {
72884         var me = this,
72885             group = me.labelsGroup,
72886             config = me.label,
72887             bbox = me.bbox,
72888             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
72889
72890         return me.chart.surface.add(Ext.apply({
72891             'type': 'text',
72892             'text-anchor': 'middle',
72893             'group': group,
72894             'x': item.point[0],
72895             'y': bbox.y + bbox.height / 2
72896         }, endLabelStyle || {}));
72897     },
72898
72899     // @private called when a label is to be created.
72900     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
72901         var me = this,
72902             chart = me.chart,
72903             resizing = chart.resizing,
72904             config = me.label,
72905             format = config.renderer,
72906             field = config.field,
72907             bbox = me.bbox,
72908             x = item.point[0],
72909             y = item.point[1],
72910             radius = item.sprite.attr.radius,
72911             bb, width, height;
72912
72913         label.setAttributes({
72914             text: format(storeItem.get(field)),
72915             hidden: true
72916         }, true);
72917
72918         if (display == 'rotate') {
72919             label.setAttributes({
72920                 'text-anchor': 'start',
72921                 'rotation': {
72922                     x: x,
72923                     y: y,
72924                     degrees: -45
72925                 }
72926             }, true);
72927             //correct label position to fit into the box
72928             bb = label.getBBox();
72929             width = bb.width;
72930             height = bb.height;
72931             x = x < bbox.x? bbox.x : x;
72932             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
72933             y = (y - height < bbox.y)? bbox.y + height : y;
72934
72935         } else if (display == 'under' || display == 'over') {
72936             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
72937             bb = item.sprite.getBBox();
72938             bb.width = bb.width || (radius * 2);
72939             bb.height = bb.height || (radius * 2);
72940             y = y + (display == 'over'? -bb.height : bb.height);
72941             //correct label position to fit into the box
72942             bb = label.getBBox();
72943             width = bb.width/2;
72944             height = bb.height/2;
72945             x = x - width < bbox.x? bbox.x + width : x;
72946             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
72947             y = y - height < bbox.y? bbox.y + height : y;
72948             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
72949         }
72950
72951         if (me.chart.animate && !me.chart.resizing) {
72952             label.show(true);
72953             me.onAnimate(label, {
72954                 to: {
72955                     x: x,
72956                     y: y
72957                 }
72958             });
72959         } else {
72960             label.setAttributes({
72961                 x: x,
72962                 y: y
72963             }, true);
72964             if (resizing && me.animation) {
72965                 me.animation.on('afteranimate', function() {
72966                     label.show(true);
72967                 });
72968             } else {
72969                 label.show(true);
72970             }
72971         }
72972     },
72973
72974     //@private Overriding highlights.js highlightItem method.
72975     highlightItem: function() {
72976         var me = this;
72977         me.callParent(arguments);
72978         if (me.line && !me.highlighted) {
72979             if (!('__strokeWidth' in me.line)) {
72980                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
72981             }
72982             if (me.line.__anim) {
72983                 me.line.__anim.paused = true;
72984             }
72985             me.line.__anim = Ext.create('Ext.fx.Anim', {
72986                 target: me.line,
72987                 to: {
72988                     'stroke-width': me.line.__strokeWidth + 3
72989                 }
72990             });
72991             me.highlighted = true;
72992         }
72993     },
72994
72995     //@private Overriding highlights.js unHighlightItem method.
72996     unHighlightItem: function() {
72997         var me = this;
72998         me.callParent(arguments);
72999         if (me.line && me.highlighted) {
73000             me.line.__anim = Ext.create('Ext.fx.Anim', {
73001                 target: me.line,
73002                 to: {
73003                     'stroke-width': me.line.__strokeWidth
73004                 }
73005             });
73006             me.highlighted = false;
73007         }
73008     },
73009
73010     //@private called when a callout needs to be placed.
73011     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
73012         if (!display) {
73013             return;
73014         }
73015
73016         var me = this,
73017             chart = me.chart,
73018             surface = chart.surface,
73019             resizing = chart.resizing,
73020             config = me.callouts,
73021             items = me.items,
73022             prev = i == 0? false : items[i -1].point,
73023             next = (i == items.length -1)? false : items[i +1].point,
73024             cur = [+item.point[0], +item.point[1]],
73025             dir, norm, normal, a, aprev, anext,
73026             offsetFromViz = config.offsetFromViz || 30,
73027             offsetToSide = config.offsetToSide || 10,
73028             offsetBox = config.offsetBox || 3,
73029             boxx, boxy, boxw, boxh,
73030             p, clipRect = me.clipRect,
73031             bbox = {
73032                 width: config.styles.width || 10,
73033                 height: config.styles.height || 10
73034             },
73035             x, y;
73036
73037         //get the right two points
73038         if (!prev) {
73039             prev = cur;
73040         }
73041         if (!next) {
73042             next = cur;
73043         }
73044         a = (next[1] - prev[1]) / (next[0] - prev[0]);
73045         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
73046         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
73047
73048         norm = Math.sqrt(1 + a * a);
73049         dir = [1 / norm, a / norm];
73050         normal = [-dir[1], dir[0]];
73051
73052         //keep the label always on the outer part of the "elbow"
73053         if (aprev > 0 && anext < 0 && normal[1] < 0
73054             || aprev < 0 && anext > 0 && normal[1] > 0) {
73055             normal[0] *= -1;
73056             normal[1] *= -1;
73057         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
73058                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
73059             normal[0] *= -1;
73060             normal[1] *= -1;
73061         }
73062         //position
73063         x = cur[0] + normal[0] * offsetFromViz;
73064         y = cur[1] + normal[1] * offsetFromViz;
73065
73066         //box position and dimensions
73067         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73068         boxy = y - bbox.height /2 - offsetBox;
73069         boxw = bbox.width + 2 * offsetBox;
73070         boxh = bbox.height + 2 * offsetBox;
73071
73072         //now check if we're out of bounds and invert the normal vector correspondingly
73073         //this may add new overlaps between labels (but labels won't be out of bounds).
73074         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
73075             normal[0] *= -1;
73076         }
73077         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
73078             normal[1] *= -1;
73079         }
73080
73081         //update positions
73082         x = cur[0] + normal[0] * offsetFromViz;
73083         y = cur[1] + normal[1] * offsetFromViz;
73084
73085         //update box position and dimensions
73086         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73087         boxy = y - bbox.height /2 - offsetBox;
73088         boxw = bbox.width + 2 * offsetBox;
73089         boxh = bbox.height + 2 * offsetBox;
73090
73091         if (chart.animate) {
73092             //set the line from the middle of the pie to the box.
73093             me.onAnimate(callout.lines, {
73094                 to: {
73095                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73096                 }
73097             });
73098             //set component position
73099             if (callout.panel) {
73100                 callout.panel.setPosition(boxx, boxy, true);
73101             }
73102         }
73103         else {
73104             //set the line from the middle of the pie to the box.
73105             callout.lines.setAttributes({
73106                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73107             }, true);
73108             //set component position
73109             if (callout.panel) {
73110                 callout.panel.setPosition(boxx, boxy);
73111             }
73112         }
73113         for (p in callout) {
73114             callout[p].show(true);
73115         }
73116     },
73117
73118     isItemInPoint: function(x, y, item, i) {
73119         var me = this,
73120             items = me.items,
73121             tolerance = me.selectionTolerance,
73122             result = null,
73123             prevItem,
73124             nextItem,
73125             prevPoint,
73126             nextPoint,
73127             ln,
73128             x1,
73129             y1,
73130             x2,
73131             y2,
73132             xIntersect,
73133             yIntersect,
73134             dist1, dist2, dist, midx, midy,
73135             sqrt = Math.sqrt, abs = Math.abs;
73136
73137         nextItem = items[i];
73138         prevItem = i && items[i - 1];
73139
73140         if (i >= ln) {
73141             prevItem = items[ln - 1];
73142         }
73143         prevPoint = prevItem && prevItem.point;
73144         nextPoint = nextItem && nextItem.point;
73145         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
73146         y1 = prevItem ? prevPoint[1] : nextPoint[1];
73147         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
73148         y2 = nextItem ? nextPoint[1] : prevPoint[1];
73149         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
73150         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
73151         dist = Math.min(dist1, dist2);
73152
73153         if (dist <= tolerance) {
73154             return dist == dist1? prevItem : nextItem;
73155         }
73156         return false;
73157     },
73158
73159     // @private toggle visibility of all series elements (markers, sprites).
73160     toggleAll: function(show) {
73161         var me = this,
73162             i, ln, shadow, shadows;
73163         if (!show) {
73164             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
73165         }
73166         else {
73167             Ext.chart.series.Cartesian.prototype.showAll.call(me);
73168         }
73169         if (me.line) {
73170             me.line.setAttributes({
73171                 hidden: !show
73172             }, true);
73173             //hide shadows too
73174             if (me.line.shadows) {
73175                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
73176                     shadow = shadows[i];
73177                     shadow.setAttributes({
73178                         hidden: !show
73179                     }, true);
73180                 }
73181             }
73182         }
73183         if (me.fillPath) {
73184             me.fillPath.setAttributes({
73185                 hidden: !show
73186             }, true);
73187         }
73188     },
73189
73190     // @private hide all series elements (markers, sprites).
73191     hideAll: function() {
73192         this.toggleAll(false);
73193     },
73194
73195     // @private hide all series elements (markers, sprites).
73196     showAll: function() {
73197         this.toggleAll(true);
73198     }
73199 });
73200
73201 /**
73202  * @class Ext.chart.series.Pie
73203  * @extends Ext.chart.series.Series
73204  *
73205  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
73206  * categories that also have a meaning as a whole.
73207  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
73208  * documentation for more information. A typical configuration object for the pie series could be:
73209  *
73210  *     @example
73211  *     var store = Ext.create('Ext.data.JsonStore', {
73212  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
73213  *         data: [
73214  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
73215  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
73216  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
73217  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
73218  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
73219  *         ]
73220  *     });
73221  *
73222  *     Ext.create('Ext.chart.Chart', {
73223  *         renderTo: Ext.getBody(),
73224  *         width: 500,
73225  *         height: 350,
73226  *         animate: true,
73227  *         store: store,
73228  *         theme: 'Base:gradients',
73229  *         series: [{
73230  *             type: 'pie',
73231  *             field: 'data1',
73232  *             showInLegend: true,
73233  *             tips: {
73234  *                 trackMouse: true,
73235  *                 width: 140,
73236  *                 height: 28,
73237  *                 renderer: function(storeItem, item) {
73238  *                     // calculate and display percentage on hover
73239  *                     var total = 0;
73240  *                     store.each(function(rec) {
73241  *                         total += rec.get('data1');
73242  *                     });
73243  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
73244  *                 }
73245  *             },
73246  *             highlight: {
73247  *                 segment: {
73248  *                     margin: 20
73249  *                 }
73250  *             },
73251  *             label: {
73252  *                 field: 'name',
73253  *                 display: 'rotate',
73254  *                 contrast: true,
73255  *                 font: '18px Arial'
73256  *             }
73257  *         }]
73258  *     });
73259  *
73260  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
73261  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
73262  *
73263  * 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
73264  * 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.
73265  *
73266  * 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
73267  * and size through the `font` parameter.
73268  *
73269  * @xtype pie
73270  */
73271 Ext.define('Ext.chart.series.Pie', {
73272
73273     /* Begin Definitions */
73274
73275     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
73276
73277     extend: 'Ext.chart.series.Series',
73278
73279     /* End Definitions */
73280
73281     type: "pie",
73282
73283     alias: 'series.pie',
73284
73285     rad: Math.PI / 180,
73286
73287     /**
73288      * @cfg {Number} highlightDuration
73289      * The duration for the pie slice highlight effect.
73290      */
73291     highlightDuration: 150,
73292
73293     /**
73294      * @cfg {String} angleField (required)
73295      * The store record field name to be used for the pie angles.
73296      * The values bound to this field name must be positive real numbers.
73297      */
73298     angleField: false,
73299
73300     /**
73301      * @cfg {String} lengthField
73302      * The store record field name to be used for the pie slice lengths.
73303      * The values bound to this field name must be positive real numbers.
73304      */
73305     lengthField: false,
73306
73307     /**
73308      * @cfg {Boolean/Number} donut
73309      * Whether to set the pie chart as donut chart.
73310      * Default's false. Can be set to a particular percentage to set the radius
73311      * of the donut chart.
73312      */
73313     donut: false,
73314
73315     /**
73316      * @cfg {Boolean} showInLegend
73317      * Whether to add the pie chart elements as legend items. Default's false.
73318      */
73319     showInLegend: false,
73320
73321     /**
73322      * @cfg {Array} colorSet
73323      * An array of color values which will be used, in order, as the pie slice fill colors.
73324      */
73325
73326     /**
73327      * @cfg {Object} style
73328      * An object containing styles for overriding series styles from Theming.
73329      */
73330     style: {},
73331
73332     constructor: function(config) {
73333         this.callParent(arguments);
73334         var me = this,
73335             chart = me.chart,
73336             surface = chart.surface,
73337             store = chart.store,
73338             shadow = chart.shadow, i, l, cfg;
73339         Ext.applyIf(me, {
73340             highlightCfg: {
73341                 segment: {
73342                     margin: 20
73343                 }
73344             }
73345         });
73346         Ext.apply(me, config, {
73347             shadowAttributes: [{
73348                 "stroke-width": 6,
73349                 "stroke-opacity": 1,
73350                 stroke: 'rgb(200, 200, 200)',
73351                 translate: {
73352                     x: 1.2,
73353                     y: 2
73354                 }
73355             },
73356             {
73357                 "stroke-width": 4,
73358                 "stroke-opacity": 1,
73359                 stroke: 'rgb(150, 150, 150)',
73360                 translate: {
73361                     x: 0.9,
73362                     y: 1.5
73363                 }
73364             },
73365             {
73366                 "stroke-width": 2,
73367                 "stroke-opacity": 1,
73368                 stroke: 'rgb(100, 100, 100)',
73369                 translate: {
73370                     x: 0.6,
73371                     y: 1
73372                 }
73373             }]
73374         });
73375         me.group = surface.getGroup(me.seriesId);
73376         if (shadow) {
73377             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
73378                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
73379             }
73380         }
73381         surface.customAttributes.segment = function(opt) {
73382             return me.getSegment(opt);
73383         };
73384         me.__excludes = me.__excludes || [];
73385     },
73386
73387     //@private updates some onbefore render parameters.
73388     initialize: function() {
73389         var me = this,
73390             store = me.chart.getChartStore();
73391         //Add yFields to be used in Legend.js
73392         me.yField = [];
73393         if (me.label.field) {
73394             store.each(function(rec) {
73395                 me.yField.push(rec.get(me.label.field));
73396             });
73397         }
73398     },
73399
73400     // @private returns an object with properties for a PieSlice.
73401     getSegment: function(opt) {
73402         var me = this,
73403             rad = me.rad,
73404             cos = Math.cos,
73405             sin = Math.sin,
73406             x = me.centerX,
73407             y = me.centerY,
73408             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
73409             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
73410             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
73411             delta = 1e-2,
73412             startAngle = opt.startAngle,
73413             endAngle = opt.endAngle,
73414             midAngle = (startAngle + endAngle) / 2 * rad,
73415             margin = opt.margin || 0,
73416             a1 = Math.min(startAngle, endAngle) * rad,
73417             a2 = Math.max(startAngle, endAngle) * rad,
73418             c1 = cos(a1), s1 = sin(a1),
73419             c2 = cos(a2), s2 = sin(a2),
73420             cm = cos(midAngle), sm = sin(midAngle),
73421             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
73422
73423         if (a2 - a1 < delta) {
73424             return {path: ""};
73425         }
73426
73427         if (margin !== 0) {
73428             x += margin * cm;
73429             y += margin * sm;
73430         }
73431
73432         x2 = x + opt.endRho * c1;
73433         y2 = y + opt.endRho * s1;
73434
73435         x4 = x + opt.endRho * c2;
73436         y4 = y + opt.endRho * s2;
73437
73438         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
73439             cm = hsqr2;
73440             sm = -hsqr2;
73441             flag = 1;
73442         }
73443
73444         x6 = x + opt.endRho * cm;
73445         y6 = y + opt.endRho * sm;
73446
73447         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
73448         // Better fix the VML engine for half circles.
73449
73450         if (opt.startRho !== 0) {
73451             x1 = x + opt.startRho * c1;
73452             y1 = y + opt.startRho * s1;
73453     
73454             x3 = x + opt.startRho * c2;
73455             y3 = y + opt.startRho * s2;
73456     
73457             x5 = x + opt.startRho * cm;
73458             y5 = y + opt.startRho * sm;
73459
73460             return {
73461                 path: [
73462                     ["M", x2, y2],
73463                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73464                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73465                     ["L", x3, y3],
73466                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
73467                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
73468                     ["Z"]
73469                 ]
73470             };
73471         } else {
73472             return {
73473                 path: [
73474                     ["M", x, y],
73475                     ["L", x2, y2],
73476                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73477                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73478                     ["L", x, y],
73479                     ["Z"]
73480                 ]
73481             };
73482         }
73483     },
73484
73485     // @private utility function to calculate the middle point of a pie slice.
73486     calcMiddle: function(item) {
73487         var me = this,
73488             rad = me.rad,
73489             slice = item.slice,
73490             x = me.centerX,
73491             y = me.centerY,
73492             startAngle = slice.startAngle,
73493             endAngle = slice.endAngle,
73494             donut = +me.donut,
73495             midAngle = -(startAngle + endAngle) * rad / 2,
73496             r = (item.endRho + item.startRho) / 2,
73497             xm = x + r * Math.cos(midAngle),
73498             ym = y - r * Math.sin(midAngle);
73499
73500         item.middle = {
73501             x: xm,
73502             y: ym
73503         };
73504     },
73505
73506     /**
73507      * Draws the series for the current chart.
73508      */
73509     drawSeries: function() {
73510         var me = this,
73511             store = me.chart.getChartStore(),
73512             group = me.group,
73513             animate = me.chart.animate,
73514             field = me.angleField || me.field || me.xField,
73515             lenField = [].concat(me.lengthField),
73516             totalLenField = 0,
73517             colors = me.colorSet,
73518             chart = me.chart,
73519             surface = chart.surface,
73520             chartBBox = chart.chartBBox,
73521             enableShadows = chart.shadow,
73522             shadowGroups = me.shadowGroups,
73523             shadowAttributes = me.shadowAttributes,
73524             lnsh = shadowGroups.length,
73525             rad = me.rad,
73526             layers = lenField.length,
73527             rhoAcum = 0,
73528             donut = +me.donut,
73529             layerTotals = [],
73530             values = {},
73531             fieldLength,
73532             items = [],
73533             passed = false,
73534             totalField = 0,
73535             maxLenField = 0,
73536             cut = 9,
73537             defcut = true,
73538             angle = 0,
73539             seriesStyle = me.seriesStyle,
73540             seriesLabelStyle = me.seriesLabelStyle,
73541             colorArrayStyle = me.colorArrayStyle,
73542             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
73543             gutterX = chart.maxGutter[0],
73544             gutterY = chart.maxGutter[1],
73545             abs = Math.abs,
73546             rendererAttributes,
73547             shadowGroup,
73548             shadowAttr,
73549             shadows,
73550             shadow,
73551             shindex,
73552             centerX,
73553             centerY,
73554             deltaRho,
73555             first = 0,
73556             slice,
73557             slices,
73558             sprite,
73559             value,
73560             item,
73561             lenValue,
73562             ln,
73563             record,
73564             i,
73565             j,
73566             startAngle,
73567             endAngle,
73568             middleAngle,
73569             sliceLength,
73570             path,
73571             p,
73572             spriteOptions, bbox;
73573
73574         Ext.apply(seriesStyle, me.style || {});
73575
73576         me.setBBox();
73577         bbox = me.bbox;
73578
73579         //override theme colors
73580         if (me.colorSet) {
73581             colorArrayStyle = me.colorSet;
73582             colorArrayLength = colorArrayStyle.length;
73583         }
73584
73585         //if not store or store is empty then there's nothing to draw
73586         if (!store || !store.getCount()) {
73587             return;
73588         }
73589
73590         me.unHighlightItem();
73591         me.cleanHighlights();
73592
73593         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
73594         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
73595         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
73596         me.slices = slices = [];
73597         me.items = items = [];
73598
73599         store.each(function(record, i) {
73600             if (this.__excludes && this.__excludes[i]) {
73601                 //hidden series
73602                 return;
73603             }
73604             totalField += +record.get(field);
73605             if (lenField[0]) {
73606                 for (j = 0, totalLenField = 0; j < layers; j++) {
73607                     totalLenField += +record.get(lenField[j]);
73608                 }
73609                 layerTotals[i] = totalLenField;
73610                 maxLenField = Math.max(maxLenField, totalLenField);
73611             }
73612         }, this);
73613
73614         totalField = totalField || 1;
73615         store.each(function(record, i) {
73616             if (this.__excludes && this.__excludes[i]) {
73617                 value = 0;
73618             } else {
73619                 value = record.get(field);
73620                 if (first == 0) {
73621                     first = 1;
73622                 }
73623             }
73624
73625             // First slice
73626             if (first == 1) {
73627                 first = 2;
73628                 me.firstAngle = angle = 360 * value / totalField / 2;
73629                 for (j = 0; j < i; j++) {
73630                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
73631                 }
73632             }
73633             
73634             endAngle = angle - 360 * value / totalField;
73635             slice = {
73636                 series: me,
73637                 value: value,
73638                 startAngle: angle,
73639                 endAngle: endAngle,
73640                 storeItem: record
73641             };
73642             if (lenField[0]) {
73643                 lenValue = layerTotals[i];
73644                 slice.rho = me.radius * (lenValue / maxLenField);
73645             } else {
73646                 slice.rho = me.radius;
73647             }
73648             slices[i] = slice;
73649             angle = endAngle;
73650         }, me);
73651         //do all shadows first.
73652         if (enableShadows) {
73653             for (i = 0, ln = slices.length; i < ln; i++) {
73654                 slice = slices[i];
73655                 slice.shadowAttrs = [];
73656                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
73657                     sprite = group.getAt(i * layers + j);
73658                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73659                     //set pie slice properties
73660                     rendererAttributes = {
73661                         segment: {
73662                             startAngle: slice.startAngle,
73663                             endAngle: slice.endAngle,
73664                             margin: 0,
73665                             rho: slice.rho,
73666                             startRho: rhoAcum + (deltaRho * donut / 100),
73667                             endRho: rhoAcum + deltaRho
73668                         },
73669                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
73670                     };
73671                     //create shadows
73672                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
73673                         shadowAttr = shadowAttributes[shindex];
73674                         shadow = shadowGroups[shindex].getAt(i);
73675                         if (!shadow) {
73676                             shadow = chart.surface.add(Ext.apply({}, {
73677                                 type: 'path',
73678                                 group: shadowGroups[shindex],
73679                                 strokeLinejoin: "round"
73680                             }, rendererAttributes, shadowAttr));
73681                         }
73682                         if (animate) {
73683                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
73684                             me.onAnimate(shadow, {
73685                                 to: shadowAttr
73686                             });
73687                         } else {
73688                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
73689                             shadow.setAttributes(shadowAttr, true);
73690                         }
73691                         shadows.push(shadow);
73692                     }
73693                     slice.shadowAttrs[j] = shadows;
73694                 }
73695             }
73696         }
73697         //do pie slices after.
73698         for (i = 0, ln = slices.length; i < ln; i++) {
73699             slice = slices[i];
73700             for (j = 0, rhoAcum = 0; j < layers; j++) {
73701                 sprite = group.getAt(i * layers + j);
73702                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73703                 //set pie slice properties
73704                 rendererAttributes = Ext.apply({
73705                     segment: {
73706                         startAngle: slice.startAngle,
73707                         endAngle: slice.endAngle,
73708                         margin: 0,
73709                         rho: slice.rho,
73710                         startRho: rhoAcum + (deltaRho * donut / 100),
73711                         endRho: rhoAcum + deltaRho
73712                     },
73713                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
73714                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73715                 item = Ext.apply({},
73716                 rendererAttributes.segment, {
73717                     slice: slice,
73718                     series: me,
73719                     storeItem: slice.storeItem,
73720                     index: i
73721                 });
73722                 me.calcMiddle(item);
73723                 if (enableShadows) {
73724                     item.shadows = slice.shadowAttrs[j];
73725                 }
73726                 items[i] = item;
73727                 // Create a new sprite if needed (no height)
73728                 if (!sprite) {
73729                     spriteOptions = Ext.apply({
73730                         type: "path",
73731                         group: group,
73732                         middle: item.middle
73733                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73734                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
73735                 }
73736                 slice.sprite = slice.sprite || [];
73737                 item.sprite = sprite;
73738                 slice.sprite.push(sprite);
73739                 slice.point = [item.middle.x, item.middle.y];
73740                 if (animate) {
73741                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
73742                     sprite._to = rendererAttributes;
73743                     sprite._animating = true;
73744                     me.onAnimate(sprite, {
73745                         to: rendererAttributes,
73746                         listeners: {
73747                             afteranimate: {
73748                                 fn: function() {
73749                                     this._animating = false;
73750                                 },
73751                                 scope: sprite
73752                             }
73753                         }
73754                     });
73755                 } else {
73756                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
73757                         hidden: false
73758                     }), i, store);
73759                     sprite.setAttributes(rendererAttributes, true);
73760                 }
73761                 rhoAcum += deltaRho;
73762             }
73763         }
73764
73765         // Hide unused bars
73766         ln = group.getCount();
73767         for (i = 0; i < ln; i++) {
73768             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
73769                 group.getAt(i).hide(true);
73770             }
73771         }
73772         if (enableShadows) {
73773             lnsh = shadowGroups.length;
73774             for (shindex = 0; shindex < ln; shindex++) {
73775                 if (!slices[(shindex / layers) >> 0]) {
73776                     for (j = 0; j < lnsh; j++) {
73777                         if (shadowGroups[j].getAt(shindex)) {
73778                             shadowGroups[j].getAt(shindex).hide(true);
73779                         }
73780                     }
73781                 }
73782             }
73783         }
73784         me.renderLabels();
73785         me.renderCallouts();
73786     },
73787
73788     // @private callback for when creating a label sprite.
73789     onCreateLabel: function(storeItem, item, i, display) {
73790         var me = this,
73791             group = me.labelsGroup,
73792             config = me.label,
73793             centerX = me.centerX,
73794             centerY = me.centerY,
73795             middle = item.middle,
73796             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
73797
73798         return me.chart.surface.add(Ext.apply({
73799             'type': 'text',
73800             'text-anchor': 'middle',
73801             'group': group,
73802             'x': middle.x,
73803             'y': middle.y
73804         }, endLabelStyle));
73805     },
73806
73807     // @private callback for when placing a label sprite.
73808     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
73809         var me = this,
73810             chart = me.chart,
73811             resizing = chart.resizing,
73812             config = me.label,
73813             format = config.renderer,
73814             field = [].concat(config.field),
73815             centerX = me.centerX,
73816             centerY = me.centerY,
73817             middle = item.middle,
73818             opt = {
73819                 x: middle.x,
73820                 y: middle.y
73821             },
73822             x = middle.x - centerX,
73823             y = middle.y - centerY,
73824             from = {},
73825             rho = 1,
73826             theta = Math.atan2(y, x || 1),
73827             dg = theta * 180 / Math.PI,
73828             prevDg;
73829         if (this.__excludes && this.__excludes[i]) {
73830             opt.hidden = true;
73831         }
73832         function fixAngle(a) {
73833             if (a < 0) {
73834                 a += 360;
73835             }
73836             return a % 360;
73837         }
73838
73839         label.setAttributes({
73840             text: format(storeItem.get(field[index]))
73841         }, true);
73842
73843         switch (display) {
73844         case 'outside':
73845             rho = Math.sqrt(x * x + y * y) * 2;
73846             //update positions
73847             opt.x = rho * Math.cos(theta) + centerX;
73848             opt.y = rho * Math.sin(theta) + centerY;
73849             break;
73850
73851         case 'rotate':
73852             dg = fixAngle(dg);
73853             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
73854
73855             prevDg = label.attr.rotation.degrees;
73856             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
73857                 if (dg > prevDg) {
73858                     dg -= 360;
73859                 } else {
73860                     dg += 360;
73861                 }
73862                 dg = dg % 360;
73863             } else {
73864                 dg = fixAngle(dg);
73865             }
73866             //update rotation angle
73867             opt.rotate = {
73868                 degrees: dg,
73869                 x: opt.x,
73870                 y: opt.y
73871             };
73872             break;
73873
73874         default:
73875             break;
73876         }
73877         //ensure the object has zero translation
73878         opt.translate = {
73879             x: 0, y: 0
73880         };
73881         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
73882             me.onAnimate(label, {
73883                 to: opt
73884             });
73885         } else {
73886             label.setAttributes(opt, true);
73887         }
73888         label._from = from;
73889     },
73890
73891     // @private callback for when placing a callout sprite.
73892     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
73893         var me = this,
73894             chart = me.chart,
73895             resizing = chart.resizing,
73896             config = me.callouts,
73897             centerX = me.centerX,
73898             centerY = me.centerY,
73899             middle = item.middle,
73900             opt = {
73901                 x: middle.x,
73902                 y: middle.y
73903             },
73904             x = middle.x - centerX,
73905             y = middle.y - centerY,
73906             rho = 1,
73907             rhoCenter,
73908             theta = Math.atan2(y, x || 1),
73909             bbox = callout.label.getBBox(),
73910             offsetFromViz = 20,
73911             offsetToSide = 10,
73912             offsetBox = 10,
73913             p;
73914
73915         //should be able to config this.
73916         rho = item.endRho + offsetFromViz;
73917         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
73918         //update positions
73919         opt.x = rho * Math.cos(theta) + centerX;
73920         opt.y = rho * Math.sin(theta) + centerY;
73921
73922         x = rhoCenter * Math.cos(theta);
73923         y = rhoCenter * Math.sin(theta);
73924
73925         if (chart.animate) {
73926             //set the line from the middle of the pie to the box.
73927             me.onAnimate(callout.lines, {
73928                 to: {
73929                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73930                 }
73931             });
73932             //set box position
73933             me.onAnimate(callout.box, {
73934                 to: {
73935                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73936                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73937                     width: bbox.width + 2 * offsetBox,
73938                     height: bbox.height + 2 * offsetBox
73939                 }
73940             });
73941             //set text position
73942             me.onAnimate(callout.label, {
73943                 to: {
73944                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73945                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73946                 }
73947             });
73948         } else {
73949             //set the line from the middle of the pie to the box.
73950             callout.lines.setAttributes({
73951                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73952             },
73953             true);
73954             //set box position
73955             callout.box.setAttributes({
73956                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73957                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73958                 width: bbox.width + 2 * offsetBox,
73959                 height: bbox.height + 2 * offsetBox
73960             },
73961             true);
73962             //set text position
73963             callout.label.setAttributes({
73964                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73965                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73966             },
73967             true);
73968         }
73969         for (p in callout) {
73970             callout[p].show(true);
73971         }
73972     },
73973
73974     // @private handles sprite animation for the series.
73975     onAnimate: function(sprite, attr) {
73976         sprite.show();
73977         return this.callParent(arguments);
73978     },
73979
73980     isItemInPoint: function(x, y, item, i) {
73981         var me = this,
73982             cx = me.centerX,
73983             cy = me.centerY,
73984             abs = Math.abs,
73985             dx = abs(x - cx),
73986             dy = abs(y - cy),
73987             startAngle = item.startAngle,
73988             endAngle = item.endAngle,
73989             rho = Math.sqrt(dx * dx + dy * dy),
73990             angle = Math.atan2(y - cy, x - cx) / me.rad;
73991
73992         // normalize to the same range of angles created by drawSeries
73993         if (angle > me.firstAngle) {
73994             angle -= 360;
73995         }
73996         return (angle <= startAngle && angle > endAngle
73997                 && rho >= item.startRho && rho <= item.endRho);
73998     },
73999
74000     // @private hides all elements in the series.
74001     hideAll: function() {
74002         var i, l, shadow, shadows, sh, lsh, sprite;
74003         if (!isNaN(this._index)) {
74004             this.__excludes = this.__excludes || [];
74005             this.__excludes[this._index] = true;
74006             sprite = this.slices[this._index].sprite;
74007             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
74008                 sprite[sh].setAttributes({
74009                     hidden: true
74010                 }, true);
74011             }
74012             if (this.slices[this._index].shadowAttrs) {
74013                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
74014                     shadow = shadows[i];
74015                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
74016                         shadow[sh].setAttributes({
74017                             hidden: true
74018                         }, true);
74019                     }
74020                 }
74021             }
74022             this.drawSeries();
74023         }
74024     },
74025
74026     // @private shows all elements in the series.
74027     showAll: function() {
74028         if (!isNaN(this._index)) {
74029             this.__excludes[this._index] = false;
74030             this.drawSeries();
74031         }
74032     },
74033
74034     /**
74035      * Highlight the specified item. If no item is provided the whole series will be highlighted.
74036      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74037      */
74038     highlightItem: function(item) {
74039         var me = this,
74040             rad = me.rad;
74041         item = item || this.items[this._index];
74042
74043         //TODO(nico): sometimes in IE itemmouseover is triggered
74044         //twice without triggering itemmouseout in between. This
74045         //fixes the highlighting bug. Eventually, events should be
74046         //changed to trigger one itemmouseout between two itemmouseovers.
74047         this.unHighlightItem();
74048
74049         if (!item || item.sprite && item.sprite._animating) {
74050             return;
74051         }
74052         me.callParent([item]);
74053         if (!me.highlight) {
74054             return;
74055         }
74056         if ('segment' in me.highlightCfg) {
74057             var highlightSegment = me.highlightCfg.segment,
74058                 animate = me.chart.animate,
74059                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
74060             //animate labels
74061             if (me.labelsGroup) {
74062                 var group = me.labelsGroup,
74063                     display = me.label.display,
74064                     label = group.getAt(item.index),
74065                     middle = (item.startAngle + item.endAngle) / 2 * rad,
74066                     r = highlightSegment.margin || 0,
74067                     x = r * Math.cos(middle),
74068                     y = r * Math.sin(middle);
74069
74070                 //TODO(nico): rounding to 1e-10
74071                 //gives the right translation. Translation
74072                 //was buggy for very small numbers. In this
74073                 //case we're not looking to translate to very small
74074                 //numbers but not to translate at all.
74075                 if (Math.abs(x) < 1e-10) {
74076                     x = 0;
74077                 }
74078                 if (Math.abs(y) < 1e-10) {
74079                     y = 0;
74080                 }
74081
74082                 if (animate) {
74083                     label.stopAnimation();
74084                     label.animate({
74085                         to: {
74086                             translate: {
74087                                 x: x,
74088                                 y: y
74089                             }
74090                         },
74091                         duration: me.highlightDuration
74092                     });
74093                 }
74094                 else {
74095                     label.setAttributes({
74096                         translate: {
74097                             x: x,
74098                             y: y
74099                         }
74100                     }, true);
74101                 }
74102             }
74103             //animate shadows
74104             if (me.chart.shadow && item.shadows) {
74105                 i = 0;
74106                 shadows = item.shadows;
74107                 ln = shadows.length;
74108                 for (; i < ln; i++) {
74109                     shadow = shadows[i];
74110                     to = {};
74111                     itemHighlightSegment = item.sprite._from.segment;
74112                     for (prop in itemHighlightSegment) {
74113                         if (! (prop in highlightSegment)) {
74114                             to[prop] = itemHighlightSegment[prop];
74115                         }
74116                     }
74117                     attrs = {
74118                         segment: Ext.applyIf(to, me.highlightCfg.segment)
74119                     };
74120                     if (animate) {
74121                         shadow.stopAnimation();
74122                         shadow.animate({
74123                             to: attrs,
74124                             duration: me.highlightDuration
74125                         });
74126                     }
74127                     else {
74128                         shadow.setAttributes(attrs, true);
74129                     }
74130                 }
74131             }
74132         }
74133     },
74134
74135     /**
74136      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
74137      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74138      */
74139     unHighlightItem: function() {
74140         var me = this;
74141         if (!me.highlight) {
74142             return;
74143         }
74144
74145         if (('segment' in me.highlightCfg) && me.items) {
74146             var items = me.items,
74147                 animate = me.chart.animate,
74148                 shadowsEnabled = !!me.chart.shadow,
74149                 group = me.labelsGroup,
74150                 len = items.length,
74151                 i = 0,
74152                 j = 0,
74153                 display = me.label.display,
74154                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
74155
74156             for (; i < len; i++) {
74157                 item = items[i];
74158                 if (!item) {
74159                     continue;
74160                 }
74161                 sprite = item.sprite;
74162                 if (sprite && sprite._highlighted) {
74163                     //animate labels
74164                     if (group) {
74165                         label = group.getAt(item.index);
74166                         attrs = Ext.apply({
74167                             translate: {
74168                                 x: 0,
74169                                 y: 0
74170                             }
74171                         },
74172                         display == 'rotate' ? {
74173                             rotate: {
74174                                 x: label.attr.x,
74175                                 y: label.attr.y,
74176                                 degrees: label.attr.rotation.degrees
74177                             }
74178                         }: {});
74179                         if (animate) {
74180                             label.stopAnimation();
74181                             label.animate({
74182                                 to: attrs,
74183                                 duration: me.highlightDuration
74184                             });
74185                         }
74186                         else {
74187                             label.setAttributes(attrs, true);
74188                         }
74189                     }
74190                     if (shadowsEnabled) {
74191                         shadows = item.shadows;
74192                         shadowLen = shadows.length;
74193                         for (; j < shadowLen; j++) {
74194                             to = {};
74195                             ihs = item.sprite._to.segment;
74196                             hs = item.sprite._from.segment;
74197                             Ext.apply(to, hs);
74198                             for (p in ihs) {
74199                                 if (! (p in hs)) {
74200                                     to[p] = ihs[p];
74201                                 }
74202                             }
74203                             shadow = shadows[j];
74204                             if (animate) {
74205                                 shadow.stopAnimation();
74206                                 shadow.animate({
74207                                     to: {
74208                                         segment: to
74209                                     },
74210                                     duration: me.highlightDuration
74211                                 });
74212                             }
74213                             else {
74214                                 shadow.setAttributes({ segment: to }, true);
74215                             }
74216                         }
74217                     }
74218                 }
74219             }
74220         }
74221         me.callParent(arguments);
74222     },
74223
74224     /**
74225      * Returns the color of the series (to be displayed as color for the series legend item).
74226      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74227      */
74228     getLegendColor: function(index) {
74229         var me = this;
74230         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
74231     }
74232 });
74233
74234
74235 /**
74236  * @class Ext.chart.series.Radar
74237  * @extends Ext.chart.series.Series
74238  *
74239  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
74240  * a constrained number of categories.
74241  *
74242  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
74243  * documentation for more information. A typical configuration object for the radar series could be:
74244  *
74245  *     @example
74246  *     var store = Ext.create('Ext.data.JsonStore', {
74247  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74248  *         data: [
74249  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74250  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74251  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74252  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74253  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74254  *         ]
74255  *     });
74256  *
74257  *     Ext.create('Ext.chart.Chart', {
74258  *         renderTo: Ext.getBody(),
74259  *         width: 500,
74260  *         height: 300,
74261  *         animate: true,
74262  *         theme:'Category2',
74263  *         store: store,
74264  *         axes: [{
74265  *             type: 'Radial',
74266  *             position: 'radial',
74267  *             label: {
74268  *                 display: true
74269  *             }
74270  *         }],
74271  *         series: [{
74272  *             type: 'radar',
74273  *             xField: 'name',
74274  *             yField: 'data3',
74275  *             showInLegend: true,
74276  *             showMarkers: true,
74277  *             markerConfig: {
74278  *                 radius: 5,
74279  *                 size: 5
74280  *             },
74281  *             style: {
74282  *                 'stroke-width': 2,
74283  *                 fill: 'none'
74284  *             }
74285  *         },{
74286  *             type: 'radar',
74287  *             xField: 'name',
74288  *             yField: 'data2',
74289  *             showMarkers: true,
74290  *             showInLegend: true,
74291  *             markerConfig: {
74292  *                 radius: 5,
74293  *                 size: 5
74294  *             },
74295  *             style: {
74296  *                 'stroke-width': 2,
74297  *                 fill: 'none'
74298  *             }
74299  *         },{
74300  *             type: 'radar',
74301  *             xField: 'name',
74302  *             yField: 'data5',
74303  *             showMarkers: true,
74304  *             showInLegend: true,
74305  *             markerConfig: {
74306  *                 radius: 5,
74307  *                 size: 5
74308  *             },
74309  *             style: {
74310  *                 'stroke-width': 2,
74311  *                 fill: 'none'
74312  *             }
74313  *         }]
74314  *     });
74315  *
74316  * In this configuration we add three series to the chart. Each of these series is bound to the same
74317  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
74318  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
74319  * for the markers of each series can be set by adding properties onto the markerConfig object.
74320  * Finally we override some theme styling properties by adding properties to the `style` object.
74321  *
74322  * @xtype radar
74323  */
74324 Ext.define('Ext.chart.series.Radar', {
74325
74326     /* Begin Definitions */
74327
74328     extend: 'Ext.chart.series.Series',
74329
74330     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
74331
74332     /* End Definitions */
74333
74334     type: "radar",
74335     alias: 'series.radar',
74336
74337
74338     rad: Math.PI / 180,
74339
74340     showInLegend: false,
74341
74342     /**
74343      * @cfg {Object} style
74344      * An object containing styles for overriding series styles from Theming.
74345      */
74346     style: {},
74347
74348     constructor: function(config) {
74349         this.callParent(arguments);
74350         var me = this,
74351             surface = me.chart.surface, i, l;
74352         me.group = surface.getGroup(me.seriesId);
74353         if (me.showMarkers) {
74354             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
74355         }
74356     },
74357
74358     /**
74359      * Draws the series for the current chart.
74360      */
74361     drawSeries: function() {
74362         var me = this,
74363             store = me.chart.getChartStore(),
74364             group = me.group,
74365             sprite,
74366             chart = me.chart,
74367             animate = chart.animate,
74368             field = me.field || me.yField,
74369             surface = chart.surface,
74370             chartBBox = chart.chartBBox,
74371             rendererAttributes,
74372             centerX, centerY,
74373             items,
74374             radius,
74375             maxValue = 0,
74376             fields = [],
74377             max = Math.max,
74378             cos = Math.cos,
74379             sin = Math.sin,
74380             pi2 = Math.PI * 2,
74381             l = store.getCount(),
74382             startPath, path, x, y, rho,
74383             i, nfields,
74384             seriesStyle = me.seriesStyle,
74385             seriesLabelStyle = me.seriesLabelStyle,
74386             first = chart.resizing || !me.radar,
74387             axis = chart.axes && chart.axes.get(0),
74388             aggregate = !(axis && axis.maximum);
74389
74390         me.setBBox();
74391
74392         maxValue = aggregate? 0 : (axis.maximum || 0);
74393
74394         Ext.apply(seriesStyle, me.style || {});
74395
74396         //if the store is empty then there's nothing to draw
74397         if (!store || !store.getCount()) {
74398             return;
74399         }
74400
74401         me.unHighlightItem();
74402         me.cleanHighlights();
74403
74404         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
74405         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
74406         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
74407         me.items = items = [];
74408
74409         if (aggregate) {
74410             //get all renderer fields
74411             chart.series.each(function(series) {
74412                 fields.push(series.yField);
74413             });
74414             //get maxValue to interpolate
74415             store.each(function(record, i) {
74416                 for (i = 0, nfields = fields.length; i < nfields; i++) {
74417                     maxValue = max(+record.get(fields[i]), maxValue);
74418                 }
74419             });
74420         }
74421         //ensure non-zero value.
74422         maxValue = maxValue || 1;
74423         //create path and items
74424         startPath = []; path = [];
74425         store.each(function(record, i) {
74426             rho = radius * record.get(field) / maxValue;
74427             x = rho * cos(i / l * pi2);
74428             y = rho * sin(i / l * pi2);
74429             if (i == 0) {
74430                 path.push('M', x + centerX, y + centerY);
74431                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
74432             } else {
74433                 path.push('L', x + centerX, y + centerY);
74434                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
74435             }
74436             items.push({
74437                 sprite: false, //TODO(nico): add markers
74438                 point: [centerX + x, centerY + y],
74439                 series: me
74440             });
74441         });
74442         path.push('Z');
74443         //create path sprite
74444         if (!me.radar) {
74445             me.radar = surface.add(Ext.apply({
74446                 type: 'path',
74447                 group: group,
74448                 path: startPath
74449             }, seriesStyle || {}));
74450         }
74451         //reset on resizing
74452         if (chart.resizing) {
74453             me.radar.setAttributes({
74454                 path: startPath
74455             }, true);
74456         }
74457         //render/animate
74458         if (chart.animate) {
74459             me.onAnimate(me.radar, {
74460                 to: Ext.apply({
74461                     path: path
74462                 }, seriesStyle || {})
74463             });
74464         } else {
74465             me.radar.setAttributes(Ext.apply({
74466                 path: path
74467             }, seriesStyle || {}), true);
74468         }
74469         //render markers, labels and callouts
74470         if (me.showMarkers) {
74471             me.drawMarkers();
74472         }
74473         me.renderLabels();
74474         me.renderCallouts();
74475     },
74476
74477     // @private draws the markers for the lines (if any).
74478     drawMarkers: function() {
74479         var me = this,
74480             chart = me.chart,
74481             surface = chart.surface,
74482             markerStyle = Ext.apply({}, me.markerStyle || {}),
74483             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
74484             items = me.items,
74485             type = endMarkerStyle.type,
74486             markerGroup = me.markerGroup,
74487             centerX = me.centerX,
74488             centerY = me.centerY,
74489             item, i, l, marker;
74490
74491         delete endMarkerStyle.type;
74492
74493         for (i = 0, l = items.length; i < l; i++) {
74494             item = items[i];
74495             marker = markerGroup.getAt(i);
74496             if (!marker) {
74497                 marker = Ext.chart.Shape[type](surface, Ext.apply({
74498                     group: markerGroup,
74499                     x: 0,
74500                     y: 0,
74501                     translate: {
74502                         x: centerX,
74503                         y: centerY
74504                     }
74505                 }, endMarkerStyle));
74506             }
74507             else {
74508                 marker.show();
74509             }
74510             if (chart.resizing) {
74511                 marker.setAttributes({
74512                     x: 0,
74513                     y: 0,
74514                     translate: {
74515                         x: centerX,
74516                         y: centerY
74517                     }
74518                 }, true);
74519             }
74520             marker._to = {
74521                 translate: {
74522                     x: item.point[0],
74523                     y: item.point[1]
74524                 }
74525             };
74526             //render/animate
74527             if (chart.animate) {
74528                 me.onAnimate(marker, {
74529                     to: marker._to
74530                 });
74531             }
74532             else {
74533                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
74534             }
74535         }
74536     },
74537
74538     isItemInPoint: function(x, y, item) {
74539         var point,
74540             tolerance = 10,
74541             abs = Math.abs;
74542         point = item.point;
74543         return (abs(point[0] - x) <= tolerance &&
74544                 abs(point[1] - y) <= tolerance);
74545     },
74546
74547     // @private callback for when creating a label sprite.
74548     onCreateLabel: function(storeItem, item, i, display) {
74549         var me = this,
74550             group = me.labelsGroup,
74551             config = me.label,
74552             centerX = me.centerX,
74553             centerY = me.centerY,
74554             point = item.point,
74555             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
74556
74557         return me.chart.surface.add(Ext.apply({
74558             'type': 'text',
74559             'text-anchor': 'middle',
74560             'group': group,
74561             'x': centerX,
74562             'y': centerY
74563         }, config || {}));
74564     },
74565
74566     // @private callback for when placing a label sprite.
74567     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
74568         var me = this,
74569             chart = me.chart,
74570             resizing = chart.resizing,
74571             config = me.label,
74572             format = config.renderer,
74573             field = config.field,
74574             centerX = me.centerX,
74575             centerY = me.centerY,
74576             opt = {
74577                 x: item.point[0],
74578                 y: item.point[1]
74579             },
74580             x = opt.x - centerX,
74581             y = opt.y - centerY;
74582
74583         label.setAttributes({
74584             text: format(storeItem.get(field)),
74585             hidden: true
74586         },
74587         true);
74588
74589         if (resizing) {
74590             label.setAttributes({
74591                 x: centerX,
74592                 y: centerY
74593             }, true);
74594         }
74595
74596         if (animate) {
74597             label.show(true);
74598             me.onAnimate(label, {
74599                 to: opt
74600             });
74601         } else {
74602             label.setAttributes(opt, true);
74603             label.show(true);
74604         }
74605     },
74606
74607     // @private for toggling (show/hide) series.
74608     toggleAll: function(show) {
74609         var me = this,
74610             i, ln, shadow, shadows;
74611         if (!show) {
74612             Ext.chart.series.Radar.superclass.hideAll.call(me);
74613         }
74614         else {
74615             Ext.chart.series.Radar.superclass.showAll.call(me);
74616         }
74617         if (me.radar) {
74618             me.radar.setAttributes({
74619                 hidden: !show
74620             }, true);
74621             //hide shadows too
74622             if (me.radar.shadows) {
74623                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
74624                     shadow = shadows[i];
74625                     shadow.setAttributes({
74626                         hidden: !show
74627                     }, true);
74628                 }
74629             }
74630         }
74631     },
74632
74633     // @private hide all elements in the series.
74634     hideAll: function() {
74635         this.toggleAll(false);
74636         this.hideMarkers(0);
74637     },
74638
74639     // @private show all elements in the series.
74640     showAll: function() {
74641         this.toggleAll(true);
74642     },
74643
74644     // @private hide all markers that belong to `markerGroup`
74645     hideMarkers: function(index) {
74646         var me = this,
74647             count = me.markerGroup && me.markerGroup.getCount() || 0,
74648             i = index || 0;
74649         for (; i < count; i++) {
74650             me.markerGroup.getAt(i).hide(true);
74651         }
74652     }
74653 });
74654
74655
74656 /**
74657  * @class Ext.chart.series.Scatter
74658  * @extends Ext.chart.series.Cartesian
74659  *
74660  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
74661  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
74662  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
74663  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
74664  *
74665  *     @example
74666  *     var store = Ext.create('Ext.data.JsonStore', {
74667  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74668  *         data: [
74669  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74670  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74671  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74672  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74673  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74674  *         ]
74675  *     });
74676  *
74677  *     Ext.create('Ext.chart.Chart', {
74678  *         renderTo: Ext.getBody(),
74679  *         width: 500,
74680  *         height: 300,
74681  *         animate: true,
74682  *         theme:'Category2',
74683  *         store: store,
74684  *         axes: [{
74685  *             type: 'Numeric',
74686  *             position: 'left',
74687  *             fields: ['data2', 'data3'],
74688  *             title: 'Sample Values',
74689  *             grid: true,
74690  *             minimum: 0
74691  *         }, {
74692  *             type: 'Category',
74693  *             position: 'bottom',
74694  *             fields: ['name'],
74695  *             title: 'Sample Metrics'
74696  *         }],
74697  *         series: [{
74698  *             type: 'scatter',
74699  *             markerConfig: {
74700  *                 radius: 5,
74701  *                 size: 5
74702  *             },
74703  *             axis: 'left',
74704  *             xField: 'name',
74705  *             yField: 'data2'
74706  *         }, {
74707  *             type: 'scatter',
74708  *             markerConfig: {
74709  *                 radius: 5,
74710  *                 size: 5
74711  *             },
74712  *             axis: 'left',
74713  *             xField: 'name',
74714  *             yField: 'data3'
74715  *         }]
74716  *     });
74717  *
74718  * 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,
74719  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
74720  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
74721  * axis to show the current values of the elements.
74722  *
74723  * @xtype scatter
74724  */
74725 Ext.define('Ext.chart.series.Scatter', {
74726
74727     /* Begin Definitions */
74728
74729     extend: 'Ext.chart.series.Cartesian',
74730
74731     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
74732
74733     /* End Definitions */
74734
74735     type: 'scatter',
74736     alias: 'series.scatter',
74737
74738     /**
74739      * @cfg {Object} markerConfig
74740      * The display style for the scatter series markers.
74741      */
74742
74743     /**
74744      * @cfg {Object} style
74745      * Append styling properties to this object for it to override theme properties.
74746      */
74747     
74748     /**
74749      * @cfg {String/Array} axis
74750      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
74751      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
74752      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
74753      */
74754
74755     constructor: function(config) {
74756         this.callParent(arguments);
74757         var me = this,
74758             shadow = me.chart.shadow,
74759             surface = me.chart.surface, i, l;
74760         Ext.apply(me, config, {
74761             style: {},
74762             markerConfig: {},
74763             shadowAttributes: [{
74764                 "stroke-width": 6,
74765                 "stroke-opacity": 0.05,
74766                 stroke: 'rgb(0, 0, 0)'
74767             }, {
74768                 "stroke-width": 4,
74769                 "stroke-opacity": 0.1,
74770                 stroke: 'rgb(0, 0, 0)'
74771             }, {
74772                 "stroke-width": 2,
74773                 "stroke-opacity": 0.15,
74774                 stroke: 'rgb(0, 0, 0)'
74775             }]
74776         });
74777         me.group = surface.getGroup(me.seriesId);
74778         if (shadow) {
74779             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
74780                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
74781             }
74782         }
74783     },
74784
74785     // @private Get chart and data boundaries
74786     getBounds: function() {
74787         var me = this,
74788             chart = me.chart,
74789             store = chart.getChartStore(),
74790             axes = [].concat(me.axis),
74791             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
74792
74793         me.setBBox();
74794         bbox = me.bbox;
74795
74796         for (i = 0, ln = axes.length; i < ln; i++) {
74797             axis = chart.axes.get(axes[i]);
74798             if (axis) {
74799                 ends = axis.calcEnds();
74800                 if (axis.position == 'top' || axis.position == 'bottom') {
74801                     minX = ends.from;
74802                     maxX = ends.to;
74803                 }
74804                 else {
74805                     minY = ends.from;
74806                     maxY = ends.to;
74807                 }
74808             }
74809         }
74810         // If a field was specified without a corresponding axis, create one to get bounds
74811         if (me.xField && !Ext.isNumber(minX)) {
74812             axis = Ext.create('Ext.chart.axis.Axis', {
74813                 chart: chart,
74814                 fields: [].concat(me.xField)
74815             }).calcEnds();
74816             minX = axis.from;
74817             maxX = axis.to;
74818         }
74819         if (me.yField && !Ext.isNumber(minY)) {
74820             axis = Ext.create('Ext.chart.axis.Axis', {
74821                 chart: chart,
74822                 fields: [].concat(me.yField)
74823             }).calcEnds();
74824             minY = axis.from;
74825             maxY = axis.to;
74826         }
74827
74828         if (isNaN(minX)) {
74829             minX = 0;
74830             maxX = store.getCount() - 1;
74831             xScale = bbox.width / (store.getCount() - 1);
74832         }
74833         else {
74834             xScale = bbox.width / (maxX - minX);
74835         }
74836
74837         if (isNaN(minY)) {
74838             minY = 0;
74839             maxY = store.getCount() - 1;
74840             yScale = bbox.height / (store.getCount() - 1);
74841         }
74842         else {
74843             yScale = bbox.height / (maxY - minY);
74844         }
74845
74846         return {
74847             bbox: bbox,
74848             minX: minX,
74849             minY: minY,
74850             xScale: xScale,
74851             yScale: yScale
74852         };
74853     },
74854
74855     // @private Build an array of paths for the chart
74856     getPaths: function() {
74857         var me = this,
74858             chart = me.chart,
74859             enableShadows = chart.shadow,
74860             store = chart.getChartStore(),
74861             group = me.group,
74862             bounds = me.bounds = me.getBounds(),
74863             bbox = me.bbox,
74864             xScale = bounds.xScale,
74865             yScale = bounds.yScale,
74866             minX = bounds.minX,
74867             minY = bounds.minY,
74868             boxX = bbox.x,
74869             boxY = bbox.y,
74870             boxHeight = bbox.height,
74871             items = me.items = [],
74872             attrs = [],
74873             x, y, xValue, yValue, sprite;
74874
74875         store.each(function(record, i) {
74876             xValue = record.get(me.xField);
74877             yValue = record.get(me.yField);
74878             //skip undefined values
74879             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
74880                 return;
74881             }
74882             // Ensure a value
74883             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
74884                 xValue = i;
74885             }
74886             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
74887                 yValue = i;
74888             }
74889             x = boxX + (xValue - minX) * xScale;
74890             y = boxY + boxHeight - (yValue - minY) * yScale;
74891             attrs.push({
74892                 x: x,
74893                 y: y
74894             });
74895
74896             me.items.push({
74897                 series: me,
74898                 value: [xValue, yValue],
74899                 point: [x, y],
74900                 storeItem: record
74901             });
74902
74903             // When resizing, reset before animating
74904             if (chart.animate && chart.resizing) {
74905                 sprite = group.getAt(i);
74906                 if (sprite) {
74907                     me.resetPoint(sprite);
74908                     if (enableShadows) {
74909                         me.resetShadow(sprite);
74910                     }
74911                 }
74912             }
74913         });
74914         return attrs;
74915     },
74916
74917     // @private translate point to the center
74918     resetPoint: function(sprite) {
74919         var bbox = this.bbox;
74920         sprite.setAttributes({
74921             translate: {
74922                 x: (bbox.x + bbox.width) / 2,
74923                 y: (bbox.y + bbox.height) / 2
74924             }
74925         }, true);
74926     },
74927
74928     // @private translate shadows of a sprite to the center
74929     resetShadow: function(sprite) {
74930         var me = this,
74931             shadows = sprite.shadows,
74932             shadowAttributes = me.shadowAttributes,
74933             ln = me.shadowGroups.length,
74934             bbox = me.bbox,
74935             i, attr;
74936         for (i = 0; i < ln; i++) {
74937             attr = Ext.apply({}, shadowAttributes[i]);
74938             if (attr.translate) {
74939                 attr.translate.x += (bbox.x + bbox.width) / 2;
74940                 attr.translate.y += (bbox.y + bbox.height) / 2;
74941             }
74942             else {
74943                 attr.translate = {
74944                     x: (bbox.x + bbox.width) / 2,
74945                     y: (bbox.y + bbox.height) / 2
74946                 };
74947             }
74948             shadows[i].setAttributes(attr, true);
74949         }
74950     },
74951
74952     // @private create a new point
74953     createPoint: function(attr, type) {
74954         var me = this,
74955             chart = me.chart,
74956             group = me.group,
74957             bbox = me.bbox;
74958
74959         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
74960             x: 0,
74961             y: 0,
74962             group: group,
74963             translate: {
74964                 x: (bbox.x + bbox.width) / 2,
74965                 y: (bbox.y + bbox.height) / 2
74966             }
74967         }, attr));
74968     },
74969
74970     // @private create a new set of shadows for a sprite
74971     createShadow: function(sprite, endMarkerStyle, type) {
74972         var me = this,
74973             chart = me.chart,
74974             shadowGroups = me.shadowGroups,
74975             shadowAttributes = me.shadowAttributes,
74976             lnsh = shadowGroups.length,
74977             bbox = me.bbox,
74978             i, shadow, shadows, attr;
74979
74980         sprite.shadows = shadows = [];
74981
74982         for (i = 0; i < lnsh; i++) {
74983             attr = Ext.apply({}, shadowAttributes[i]);
74984             if (attr.translate) {
74985                 attr.translate.x += (bbox.x + bbox.width) / 2;
74986                 attr.translate.y += (bbox.y + bbox.height) / 2;
74987             }
74988             else {
74989                 Ext.apply(attr, {
74990                     translate: {
74991                         x: (bbox.x + bbox.width) / 2,
74992                         y: (bbox.y + bbox.height) / 2
74993                     }
74994                 });
74995             }
74996             Ext.apply(attr, endMarkerStyle);
74997             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
74998                 x: 0,
74999                 y: 0,
75000                 group: shadowGroups[i]
75001             }, attr));
75002             shadows.push(shadow);
75003         }
75004     },
75005
75006     /**
75007      * Draws the series for the current chart.
75008      */
75009     drawSeries: function() {
75010         var me = this,
75011             chart = me.chart,
75012             store = chart.getChartStore(),
75013             group = me.group,
75014             enableShadows = chart.shadow,
75015             shadowGroups = me.shadowGroups,
75016             shadowAttributes = me.shadowAttributes,
75017             lnsh = shadowGroups.length,
75018             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
75019             rendererAttributes, shadowAttribute;
75020
75021         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
75022         type = endMarkerStyle.type;
75023         delete endMarkerStyle.type;
75024
75025         //if the store is empty then there's nothing to be rendered
75026         if (!store || !store.getCount()) {
75027             return;
75028         }
75029
75030         me.unHighlightItem();
75031         me.cleanHighlights();
75032
75033         attrs = me.getPaths();
75034         ln = attrs.length;
75035         for (i = 0; i < ln; i++) {
75036             attr = attrs[i];
75037             sprite = group.getAt(i);
75038             Ext.apply(attr, endMarkerStyle);
75039
75040             // Create a new sprite if needed (no height)
75041             if (!sprite) {
75042                 sprite = me.createPoint(attr, type);
75043                 if (enableShadows) {
75044                     me.createShadow(sprite, endMarkerStyle, type);
75045                 }
75046             }
75047
75048             shadows = sprite.shadows;
75049             if (chart.animate) {
75050                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75051                 sprite._to = rendererAttributes;
75052                 me.onAnimate(sprite, {
75053                     to: rendererAttributes
75054                 });
75055                 //animate shadows
75056                 for (shindex = 0; shindex < lnsh; shindex++) {
75057                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75058                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75059                         hidden: false,
75060                         translate: {
75061                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75062                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75063                         }
75064                     }, shadowAttribute), i, store);
75065                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
75066                 }
75067             }
75068             else {
75069                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75070                 sprite._to = rendererAttributes;
75071                 sprite.setAttributes(rendererAttributes, true);
75072                 //animate shadows
75073                 for (shindex = 0; shindex < lnsh; shindex++) {
75074                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75075                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75076                         hidden: false,
75077                         translate: {
75078                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75079                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75080                         } 
75081                     }, shadowAttribute), i, store);
75082                     shadows[shindex].setAttributes(rendererAttributes, true);
75083                 }
75084             }
75085             me.items[i].sprite = sprite;
75086         }
75087
75088         // Hide unused sprites
75089         ln = group.getCount();
75090         for (i = attrs.length; i < ln; i++) {
75091             group.getAt(i).hide(true);
75092         }
75093         me.renderLabels();
75094         me.renderCallouts();
75095     },
75096
75097     // @private callback for when creating a label sprite.
75098     onCreateLabel: function(storeItem, item, i, display) {
75099         var me = this,
75100             group = me.labelsGroup,
75101             config = me.label,
75102             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
75103             bbox = me.bbox;
75104
75105         return me.chart.surface.add(Ext.apply({
75106             type: 'text',
75107             group: group,
75108             x: item.point[0],
75109             y: bbox.y + bbox.height / 2
75110         }, endLabelStyle));
75111     },
75112
75113     // @private callback for when placing a label sprite.
75114     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75115         var me = this,
75116             chart = me.chart,
75117             resizing = chart.resizing,
75118             config = me.label,
75119             format = config.renderer,
75120             field = config.field,
75121             bbox = me.bbox,
75122             x = item.point[0],
75123             y = item.point[1],
75124             radius = item.sprite.attr.radius,
75125             bb, width, height, anim;
75126
75127         label.setAttributes({
75128             text: format(storeItem.get(field)),
75129             hidden: true
75130         }, true);
75131
75132         if (display == 'rotate') {
75133             label.setAttributes({
75134                 'text-anchor': 'start',
75135                 'rotation': {
75136                     x: x,
75137                     y: y,
75138                     degrees: -45
75139                 }
75140             }, true);
75141             //correct label position to fit into the box
75142             bb = label.getBBox();
75143             width = bb.width;
75144             height = bb.height;
75145             x = x < bbox.x? bbox.x : x;
75146             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
75147             y = (y - height < bbox.y)? bbox.y + height : y;
75148
75149         } else if (display == 'under' || display == 'over') {
75150             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
75151             bb = item.sprite.getBBox();
75152             bb.width = bb.width || (radius * 2);
75153             bb.height = bb.height || (radius * 2);
75154             y = y + (display == 'over'? -bb.height : bb.height);
75155             //correct label position to fit into the box
75156             bb = label.getBBox();
75157             width = bb.width/2;
75158             height = bb.height/2;
75159             x = x - width < bbox.x ? bbox.x + width : x;
75160             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
75161             y = y - height < bbox.y? bbox.y + height : y;
75162             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
75163         }
75164
75165         if (!chart.animate) {
75166             label.setAttributes({
75167                 x: x,
75168                 y: y
75169             }, true);
75170             label.show(true);
75171         }
75172         else {
75173             if (resizing) {
75174                 anim = item.sprite.getActiveAnimation();
75175                 if (anim) {
75176                     anim.on('afteranimate', function() {
75177                         label.setAttributes({
75178                             x: x,
75179                             y: y
75180                         }, true);
75181                         label.show(true);
75182                     });
75183                 }
75184                 else {
75185                     label.show(true);
75186                 }
75187             }
75188             else {
75189                 me.onAnimate(label, {
75190                     to: {
75191                         x: x,
75192                         y: y
75193                     }
75194                 });
75195             }
75196         }
75197     },
75198
75199     // @private callback for when placing a callout sprite.
75200     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
75201         var me = this,
75202             chart = me.chart,
75203             surface = chart.surface,
75204             resizing = chart.resizing,
75205             config = me.callouts,
75206             items = me.items,
75207             cur = item.point,
75208             normal,
75209             bbox = callout.label.getBBox(),
75210             offsetFromViz = 30,
75211             offsetToSide = 10,
75212             offsetBox = 3,
75213             boxx, boxy, boxw, boxh,
75214             p, clipRect = me.bbox,
75215             x, y;
75216
75217         //position
75218         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
75219         x = cur[0] + normal[0] * offsetFromViz;
75220         y = cur[1] + normal[1] * offsetFromViz;
75221
75222         //box position and dimensions
75223         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75224         boxy = y - bbox.height /2 - offsetBox;
75225         boxw = bbox.width + 2 * offsetBox;
75226         boxh = bbox.height + 2 * offsetBox;
75227
75228         //now check if we're out of bounds and invert the normal vector correspondingly
75229         //this may add new overlaps between labels (but labels won't be out of bounds).
75230         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
75231             normal[0] *= -1;
75232         }
75233         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
75234             normal[1] *= -1;
75235         }
75236
75237         //update positions
75238         x = cur[0] + normal[0] * offsetFromViz;
75239         y = cur[1] + normal[1] * offsetFromViz;
75240
75241         //update box position and dimensions
75242         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75243         boxy = y - bbox.height /2 - offsetBox;
75244         boxw = bbox.width + 2 * offsetBox;
75245         boxh = bbox.height + 2 * offsetBox;
75246
75247         if (chart.animate) {
75248             //set the line from the middle of the pie to the box.
75249             me.onAnimate(callout.lines, {
75250                 to: {
75251                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75252                 }
75253             }, true);
75254             //set box position
75255             me.onAnimate(callout.box, {
75256                 to: {
75257                     x: boxx,
75258                     y: boxy,
75259                     width: boxw,
75260                     height: boxh
75261                 }
75262             }, true);
75263             //set text position
75264             me.onAnimate(callout.label, {
75265                 to: {
75266                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75267                     y: y
75268                 }
75269             }, true);
75270         } else {
75271             //set the line from the middle of the pie to the box.
75272             callout.lines.setAttributes({
75273                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75274             }, true);
75275             //set box position
75276             callout.box.setAttributes({
75277                 x: boxx,
75278                 y: boxy,
75279                 width: boxw,
75280                 height: boxh
75281             }, true);
75282             //set text position
75283             callout.label.setAttributes({
75284                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75285                 y: y
75286             }, true);
75287         }
75288         for (p in callout) {
75289             callout[p].show(true);
75290         }
75291     },
75292
75293     // @private handles sprite animation for the series.
75294     onAnimate: function(sprite, attr) {
75295         sprite.show();
75296         return this.callParent(arguments);
75297     },
75298
75299     isItemInPoint: function(x, y, item) {
75300         var point,
75301             tolerance = 10,
75302             abs = Math.abs;
75303
75304         function dist(point) {
75305             var dx = abs(point[0] - x),
75306                 dy = abs(point[1] - y);
75307             return Math.sqrt(dx * dx + dy * dy);
75308         }
75309         point = item.point;
75310         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
75311             point[1] - tolerance <= y && point[1] + tolerance >= y);
75312     }
75313 });
75314
75315
75316 /**
75317  * @class Ext.chart.theme.Base
75318  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
75319  * @ignore
75320  */
75321 Ext.define('Ext.chart.theme.Base', {
75322
75323     /* Begin Definitions */
75324
75325     requires: ['Ext.chart.theme.Theme'],
75326
75327     /* End Definitions */
75328
75329     constructor: function(config) {
75330         Ext.chart.theme.call(this, config, {
75331             background: false,
75332             axis: {
75333                 stroke: '#444',
75334                 'stroke-width': 1
75335             },
75336             axisLabelTop: {
75337                 fill: '#444',
75338                 font: '12px Arial, Helvetica, sans-serif',
75339                 spacing: 2,
75340                 padding: 5,
75341                 renderer: function(v) { return v; }
75342             },
75343             axisLabelRight: {
75344                 fill: '#444',
75345                 font: '12px Arial, Helvetica, sans-serif',
75346                 spacing: 2,
75347                 padding: 5,
75348                 renderer: function(v) { return v; }
75349             },
75350             axisLabelBottom: {
75351                 fill: '#444',
75352                 font: '12px Arial, Helvetica, sans-serif',
75353                 spacing: 2,
75354                 padding: 5,
75355                 renderer: function(v) { return v; }
75356             },
75357             axisLabelLeft: {
75358                 fill: '#444',
75359                 font: '12px Arial, Helvetica, sans-serif',
75360                 spacing: 2,
75361                 padding: 5,
75362                 renderer: function(v) { return v; }
75363             },
75364             axisTitleTop: {
75365                 font: 'bold 18px Arial',
75366                 fill: '#444'
75367             },
75368             axisTitleRight: {
75369                 font: 'bold 18px Arial',
75370                 fill: '#444',
75371                 rotate: {
75372                     x:0, y:0,
75373                     degrees: 270
75374                 }
75375             },
75376             axisTitleBottom: {
75377                 font: 'bold 18px Arial',
75378                 fill: '#444'
75379             },
75380             axisTitleLeft: {
75381                 font: 'bold 18px Arial',
75382                 fill: '#444',
75383                 rotate: {
75384                     x:0, y:0,
75385                     degrees: 270
75386                 }
75387             },
75388             series: {
75389                 'stroke-width': 0
75390             },
75391             seriesLabel: {
75392                 font: '12px Arial',
75393                 fill: '#333'
75394             },
75395             marker: {
75396                 stroke: '#555',
75397                 fill: '#000',
75398                 radius: 3,
75399                 size: 3
75400             },
75401             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
75402             seriesThemes: [{
75403                 fill: "#115fa6"
75404             }, {
75405                 fill: "#94ae0a"
75406             }, {
75407                 fill: "#a61120"
75408             }, {
75409                 fill: "#ff8809"
75410             }, {
75411                 fill: "#ffd13e"
75412             }, {
75413                 fill: "#a61187"
75414             }, {
75415                 fill: "#24ad9a"
75416             }, {
75417                 fill: "#7c7474"
75418             }, {
75419                 fill: "#a66111"
75420             }],
75421             markerThemes: [{
75422                 fill: "#115fa6",
75423                 type: 'circle' 
75424             }, {
75425                 fill: "#94ae0a",
75426                 type: 'cross'
75427             }, {
75428                 fill: "#a61120",
75429                 type: 'plus'
75430             }]
75431         });
75432     }
75433 }, function() {
75434     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
75435         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
75436         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
75437         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
75438                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
75439                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
75440                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
75441                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
75442                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
75443         cats = categories.length;
75444     
75445     //Create themes from base colors
75446     for (; i < l; i++) {
75447         themes[names[i]] = (function(color) {
75448             return Ext.extend(themes.Base, {
75449                 constructor: function(config) {
75450                     themes.Base.prototype.constructor.call(this, Ext.apply({
75451                         baseColor: color
75452                     }, config));
75453                 }
75454             });
75455         })(palette[i]);
75456     }
75457     
75458     //Create theme from color array
75459     for (i = 0; i < cats; i++) {
75460         themes['Category' + (i + 1)] = (function(category) {
75461             return Ext.extend(themes.Base, {
75462                 constructor: function(config) {
75463                     themes.Base.prototype.constructor.call(this, Ext.apply({
75464                         colors: category
75465                     }, config));
75466                 }
75467             });
75468         })(categories[i]);
75469     }
75470 });
75471
75472 /**
75473  * @author Ed Spencer
75474  *
75475  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
75476  * automatically configured with a {@link Ext.data.reader.Array}.
75477  *
75478  * A store configuration would be something like:
75479  *
75480  *     var store = Ext.create('Ext.data.ArrayStore', {
75481  *         // store configs
75482  *         autoDestroy: true,
75483  *         storeId: 'myStore',
75484  *         // reader configs
75485  *         idIndex: 0,
75486  *         fields: [
75487  *            'company',
75488  *            {name: 'price', type: 'float'},
75489  *            {name: 'change', type: 'float'},
75490  *            {name: 'pctChange', type: 'float'},
75491  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
75492  *         ]
75493  *     });
75494  *
75495  * This store is configured to consume a returned object of the form:
75496  *
75497  *     var myData = [
75498  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
75499  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
75500  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
75501  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
75502  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
75503  *     ];
75504  *
75505  * An object literal of this form could also be used as the {@link #data} config option.
75506  *
75507  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
75508  */
75509 Ext.define('Ext.data.ArrayStore', {
75510     extend: 'Ext.data.Store',
75511     alias: 'store.array',
75512     uses: ['Ext.data.reader.Array'],
75513
75514     constructor: function(config) {
75515         config = config || {};
75516
75517         Ext.applyIf(config, {
75518             proxy: {
75519                 type: 'memory',
75520                 reader: 'array'
75521             }
75522         });
75523
75524         this.callParent([config]);
75525     },
75526
75527     loadData: function(data, append) {
75528         if (this.expandData === true) {
75529             var r = [],
75530                 i = 0,
75531                 ln = data.length;
75532
75533             for (; i < ln; i++) {
75534                 r[r.length] = [data[i]];
75535             }
75536
75537             data = r;
75538         }
75539
75540         this.callParent([data, append]);
75541     }
75542 }, function() {
75543     // backwards compat
75544     Ext.data.SimpleStore = Ext.data.ArrayStore;
75545     // Ext.reg('simplestore', Ext.data.SimpleStore);
75546 });
75547
75548 /**
75549  * @author Ed Spencer
75550  * @class Ext.data.Batch
75551  *
75552  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
75553  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
75554  * event if any of the Operations encounter an exception.</p>
75555  *
75556  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
75557  *
75558  */
75559 Ext.define('Ext.data.Batch', {
75560     mixins: {
75561         observable: 'Ext.util.Observable'
75562     },
75563
75564     /**
75565      * @property {Boolean} autoStart
75566      * True to immediately start processing the batch as soon as it is constructed.
75567      */
75568     autoStart: false,
75569
75570     /**
75571      * @property {Number} current
75572      * The index of the current operation being executed
75573      */
75574     current: -1,
75575
75576     /**
75577      * @property {Number} total
75578      * The total number of operations in this batch. Read only
75579      */
75580     total: 0,
75581
75582     /**
75583      * @property {Boolean} isRunning
75584      * True if the batch is currently running
75585      */
75586     isRunning: false,
75587
75588     /**
75589      * @property {Boolean} isComplete
75590      * True if this batch has been executed completely
75591      */
75592     isComplete: false,
75593
75594     /**
75595      * @property {Boolean} hasException
75596      * True if this batch has encountered an exception. This is cleared at the start of each operation
75597      */
75598     hasException: false,
75599
75600     /**
75601      * @property {Boolean} pauseOnException
75602      * True to automatically pause the execution of the batch if any operation encounters an exception
75603      */
75604     pauseOnException: true,
75605
75606     /**
75607      * Creates new Batch object.
75608      * @param {Object} [config] Config object
75609      */
75610     constructor: function(config) {
75611         var me = this;
75612
75613         me.addEvents(
75614           /**
75615            * @event complete
75616            * Fired when all operations of this batch have been completed
75617            * @param {Ext.data.Batch} batch The batch object
75618            * @param {Object} operation The last operation that was executed
75619            */
75620           'complete',
75621
75622           /**
75623            * @event exception
75624            * Fired when a operation encountered an exception
75625            * @param {Ext.data.Batch} batch The batch object
75626            * @param {Object} operation The operation that encountered the exception
75627            */
75628           'exception',
75629
75630           /**
75631            * @event operationcomplete
75632            * Fired when each operation of the batch completes
75633            * @param {Ext.data.Batch} batch The batch object
75634            * @param {Object} operation The operation that just completed
75635            */
75636           'operationcomplete'
75637         );
75638
75639         me.mixins.observable.constructor.call(me, config);
75640
75641         /**
75642          * Ordered array of operations that will be executed by this batch
75643          * @property {Ext.data.Operation[]} operations
75644          */
75645         me.operations = [];
75646     },
75647
75648     /**
75649      * Adds a new operation to this batch
75650      * @param {Object} operation The {@link Ext.data.Operation Operation} object
75651      */
75652     add: function(operation) {
75653         this.total++;
75654
75655         operation.setBatch(this);
75656
75657         this.operations.push(operation);
75658     },
75659
75660     /**
75661      * Kicks off the execution of the batch, continuing from the next operation if the previous
75662      * operation encountered an exception, or if execution was paused
75663      */
75664     start: function() {
75665         this.hasException = false;
75666         this.isRunning = true;
75667
75668         this.runNextOperation();
75669     },
75670
75671     /**
75672      * @private
75673      * Runs the next operation, relative to this.current.
75674      */
75675     runNextOperation: function() {
75676         this.runOperation(this.current + 1);
75677     },
75678
75679     /**
75680      * Pauses execution of the batch, but does not cancel the current operation
75681      */
75682     pause: function() {
75683         this.isRunning = false;
75684     },
75685
75686     /**
75687      * Executes a operation by its numeric index
75688      * @param {Number} index The operation index to run
75689      */
75690     runOperation: function(index) {
75691         var me = this,
75692             operations = me.operations,
75693             operation  = operations[index],
75694             onProxyReturn;
75695
75696         if (operation === undefined) {
75697             me.isRunning  = false;
75698             me.isComplete = true;
75699             me.fireEvent('complete', me, operations[operations.length - 1]);
75700         } else {
75701             me.current = index;
75702
75703             onProxyReturn = function(operation) {
75704                 var hasException = operation.hasException();
75705
75706                 if (hasException) {
75707                     me.hasException = true;
75708                     me.fireEvent('exception', me, operation);
75709                 } else {
75710                     me.fireEvent('operationcomplete', me, operation);
75711                 }
75712
75713                 if (hasException && me.pauseOnException) {
75714                     me.pause();
75715                 } else {
75716                     operation.setCompleted();
75717                     me.runNextOperation();
75718                 }
75719             };
75720
75721             operation.setStarted();
75722
75723             me.proxy[operation.action](operation, onProxyReturn, me);
75724         }
75725     }
75726 });
75727 /**
75728  * @author Ed Spencer
75729  * @class Ext.data.BelongsToAssociation
75730  * @extends Ext.data.Association
75731  *
75732  * Represents a many to one association with another model. The owner model is expected to have
75733  * a foreign key which references the primary key of the associated model:
75734  *
75735  *     Ext.define('Category', {
75736  *         extend: 'Ext.data.Model',
75737  *         fields: [
75738  *             { name: 'id',   type: 'int' },
75739  *             { name: 'name', type: 'string' }
75740  *         ]
75741  *     });
75742  *
75743  *     Ext.define('Product', {
75744  *         extend: 'Ext.data.Model',
75745  *         fields: [
75746  *             { name: 'id',          type: 'int' },
75747  *             { name: 'category_id', type: 'int' },
75748  *             { name: 'name',        type: 'string' }
75749  *         ],
75750  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
75751  *         associations: [
75752  *             { type: 'belongsTo', model: 'Category' }
75753  *         ]
75754  *     });
75755  *
75756  * In the example above we have created models for Products and Categories, and linked them together
75757  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
75758  * based on the Product's category_id, and provides new functions on the Product model:
75759  *
75760  * ## Generated getter function
75761  *
75762  * The first function that is added to the owner model is a getter function:
75763  *
75764  *     var product = new Product({
75765  *         id: 100,
75766  *         category_id: 20,
75767  *         name: 'Sneakers'
75768  *     });
75769  *
75770  *     product.getCategory(function(category, operation) {
75771  *         // do something with the category object
75772  *         alert(category.get('id')); // alerts 20
75773  *     }, this);
75774  *
75775  * The getCategory function was created on the Product model when we defined the association. This uses the
75776  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
75777  * callback when it has loaded.
75778  *
75779  * The new getCategory function will also accept an object containing success, failure and callback properties
75780  * - callback will always be called, success will only be called if the associated model was loaded successfully
75781  * and failure will only be called if the associatied model could not be loaded:
75782  *
75783  *     product.getCategory({
75784  *         callback: function(category, operation) {}, // a function that will always be called
75785  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
75786  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
75787  *         scope   : this // optionally pass in a scope object to execute the callbacks in
75788  *     });
75789  *
75790  * In each case above the callbacks are called with two arguments - the associated model instance and the
75791  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
75792  * useful when the instance could not be loaded.
75793  *
75794  * ## Generated setter function
75795  *
75796  * The second generated function sets the associated model instance - if only a single argument is passed to
75797  * the setter then the following two calls are identical:
75798  *
75799  *     // this call...
75800  *     product.setCategory(10);
75801  *
75802  *     // is equivalent to this call:
75803  *     product.set('category_id', 10);
75804  *
75805  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
75806  * the owner model's {@link Ext.data.Model#save save} method:
75807  *
75808  *     product.setCategory(10, function(product, operation) {
75809  *         // the product has been saved
75810  *         alert(product.get('category_id')); //now alerts 10
75811  *     });
75812  *
75813  *     //alternative syntax:
75814  *     product.setCategory(10, {
75815  *         callback: function(product, operation), // a function that will always be called
75816  *         success : function(product, operation), // a function that will only be called if the load succeeded
75817  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
75818  *         scope   : this //optionally pass in a scope object to execute the callbacks in
75819  *     })
75820  *
75821  * ## Customisation
75822  *
75823  * Associations reflect on the models they are linking to automatically set up properties such as the
75824  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
75825  *
75826  *     Ext.define('Product', {
75827  *         fields: [...],
75828  *
75829  *         associations: [
75830  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
75831  *         ]
75832  *     });
75833  *
75834  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
75835  * with our own settings. Usually this will not be needed.
75836  */
75837 Ext.define('Ext.data.BelongsToAssociation', {
75838     extend: 'Ext.data.Association',
75839
75840     alias: 'association.belongsto',
75841
75842     /**
75843      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
75844      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
75845      * model called Product would set up a product_id foreign key.
75846      *
75847      *     Ext.define('Order', {
75848      *         extend: 'Ext.data.Model',
75849      *         fields: ['id', 'date'],
75850      *         hasMany: 'Product'
75851      *     });
75852      *
75853      *     Ext.define('Product', {
75854      *         extend: 'Ext.data.Model',
75855      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
75856      *         belongsTo: 'Group'
75857      *     });
75858      *     var product = new Product({
75859      *         id: 1,
75860      *         name: 'Product 1',
75861      *         order_id: 22
75862      *     }, 1);
75863      *     product.getOrder(); // Will make a call to the server asking for order_id 22
75864      *
75865      */
75866
75867     /**
75868      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
75869      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
75870      */
75871
75872     /**
75873      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
75874      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
75875      */
75876
75877     /**
75878      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
75879      * Use 'belongsTo' to create a HasManyAssocation
75880      *
75881      *     associations: [{
75882      *         type: 'belongsTo',
75883      *         model: 'User'
75884      *     }]
75885      */
75886     constructor: function(config) {
75887         this.callParent(arguments);
75888
75889         var me             = this,
75890             ownerProto     = me.ownerModel.prototype,
75891             associatedName = me.associatedName,
75892             getterName     = me.getterName || 'get' + associatedName,
75893             setterName     = me.setterName || 'set' + associatedName;
75894
75895         Ext.applyIf(me, {
75896             name        : associatedName,
75897             foreignKey  : associatedName.toLowerCase() + "_id",
75898             instanceName: associatedName + 'BelongsToInstance',
75899             associationKey: associatedName.toLowerCase()
75900         });
75901
75902         ownerProto[getterName] = me.createGetter();
75903         ownerProto[setterName] = me.createSetter();
75904     },
75905
75906     /**
75907      * @private
75908      * Returns a setter function to be placed on the owner model's prototype
75909      * @return {Function} The setter function
75910      */
75911     createSetter: function() {
75912         var me              = this,
75913             ownerModel      = me.ownerModel,
75914             associatedModel = me.associatedModel,
75915             foreignKey      = me.foreignKey,
75916             primaryKey      = me.primaryKey;
75917
75918         //'this' refers to the Model instance inside this function
75919         return function(value, options, scope) {
75920             this.set(foreignKey, value);
75921
75922             if (typeof options == 'function') {
75923                 options = {
75924                     callback: options,
75925                     scope: scope || this
75926                 };
75927             }
75928
75929             if (Ext.isObject(options)) {
75930                 return this.save(options);
75931             }
75932         };
75933     },
75934
75935     /**
75936      * @private
75937      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
75938      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
75939      * @return {Function} The getter function
75940      */
75941     createGetter: function() {
75942         var me              = this,
75943             ownerModel      = me.ownerModel,
75944             associatedName  = me.associatedName,
75945             associatedModel = me.associatedModel,
75946             foreignKey      = me.foreignKey,
75947             primaryKey      = me.primaryKey,
75948             instanceName    = me.instanceName;
75949
75950         //'this' refers to the Model instance inside this function
75951         return function(options, scope) {
75952             options = options || {};
75953
75954             var model = this,
75955                 foreignKeyId = model.get(foreignKey),
75956                 instance,
75957                 args;
75958
75959             if (model[instanceName] === undefined) {
75960                 instance = Ext.ModelManager.create({}, associatedName);
75961                 instance.set(primaryKey, foreignKeyId);
75962
75963                 if (typeof options == 'function') {
75964                     options = {
75965                         callback: options,
75966                         scope: scope || model
75967                     };
75968                 }
75969
75970                 associatedModel.load(foreignKeyId, options);
75971                 model[instanceName] = associatedModel;
75972                 return associatedModel;
75973             } else {
75974                 instance = model[instanceName];
75975                 args = [instance];
75976                 scope = scope || model;
75977
75978                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
75979                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
75980                 //instead of the association layer.
75981                 Ext.callback(options, scope, args);
75982                 Ext.callback(options.success, scope, args);
75983                 Ext.callback(options.failure, scope, args);
75984                 Ext.callback(options.callback, scope, args);
75985
75986                 return instance;
75987             }
75988         };
75989     },
75990
75991     /**
75992      * Read associated data
75993      * @private
75994      * @param {Ext.data.Model} record The record we're writing to
75995      * @param {Ext.data.reader.Reader} reader The reader for the associated model
75996      * @param {Object} associationData The raw associated data
75997      */
75998     read: function(record, reader, associationData){
75999         record[this.instanceName] = reader.read([associationData]).records[0];
76000     }
76001 });
76002
76003 /**
76004  * @class Ext.data.BufferStore
76005  * @extends Ext.data.Store
76006  * @ignore
76007  */
76008 Ext.define('Ext.data.BufferStore', {
76009     extend: 'Ext.data.Store',
76010     alias: 'store.buffer',
76011     sortOnLoad: false,
76012     filterOnLoad: false,
76013     
76014     constructor: function() {
76015         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
76016     }
76017 });
76018 /**
76019  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
76020  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
76021  * error conditions, etc).
76022  *
76023  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
76024  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
76025  *
76026  * # Specification
76027  *
76028  * For additional information consult the [Ext.Direct Specification][1].
76029  *
76030  * # Providers
76031  *
76032  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
76033  * server. There are several providers that exist in the core at the moment:
76034  *
76035  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
76036  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
76037  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
76038  *
76039  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
76040  *
76041  * # Router
76042  *
76043  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
76044  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
76045  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
76046  *
76047  * # Server side events
76048  *
76049  * Custom events from the server may be handled by the client by adding listeners, for example:
76050  *
76051  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
76052  *
76053  *     // add a handler for a 'message' event sent by the server
76054  *     Ext.direct.Manager.on('message', function(e){
76055  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
76056  *         out.el.scrollTo('t', 100000, true);
76057  *     });
76058  *
76059  *    [1]: http://sencha.com/products/extjs/extdirect
76060  *
76061  * @singleton
76062  * @alternateClassName Ext.Direct
76063  */
76064 Ext.define('Ext.direct.Manager', {
76065
76066     /* Begin Definitions */
76067     singleton: true,
76068
76069     mixins: {
76070         observable: 'Ext.util.Observable'
76071     },
76072
76073     requires: ['Ext.util.MixedCollection'],
76074
76075     statics: {
76076         exceptions: {
76077             TRANSPORT: 'xhr',
76078             PARSE: 'parse',
76079             LOGIN: 'login',
76080             SERVER: 'exception'
76081         }
76082     },
76083
76084     /* End Definitions */
76085
76086     constructor: function(){
76087         var me = this;
76088
76089         me.addEvents(
76090             /**
76091              * @event event
76092              * Fires after an event.
76093              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
76094              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76095              */
76096             'event',
76097             /**
76098              * @event exception
76099              * Fires after an event exception.
76100              * @param {Ext.direct.Event} e The event type that occurred.
76101              */
76102             'exception'
76103         );
76104         me.transactions = Ext.create('Ext.util.MixedCollection');
76105         me.providers = Ext.create('Ext.util.MixedCollection');
76106
76107         me.mixins.observable.constructor.call(me);
76108     },
76109
76110     /**
76111      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
76112      * is not already connected, it will auto-connect.
76113      *
76114      *     var pollProv = new Ext.direct.PollingProvider({
76115      *         url: 'php/poll2.php'
76116      *     });
76117      *
76118      *     Ext.direct.Manager.addProvider({
76119      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
76120      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
76121      *         "actions":{              // each property within the actions object represents a Class
76122      *             "TestAction":[       // array of methods within each server side Class
76123      *             {
76124      *                 "name":"doEcho", // name of method
76125      *                 "len":1
76126      *             },{
76127      *                 "name":"multiply",
76128      *                 "len":1
76129      *             },{
76130      *                 "name":"doForm",
76131      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
76132      *                 "len":1
76133      *             }]
76134      *         },
76135      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
76136      *     },{
76137      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
76138      *         url:  'php/poll.php'
76139      *     }, pollProv); // reference to previously created instance
76140      *
76141      * @param {Ext.direct.Provider/Object...} provider
76142      * Accepts any number of Provider descriptions (an instance or config object for
76143      * a Provider). Each Provider description instructs Ext.Directhow to create
76144      * client-side stub methods.
76145      */
76146     addProvider : function(provider){
76147         var me = this,
76148             args = arguments,
76149             i = 0,
76150             len;
76151
76152         if (args.length > 1) {
76153             for (len = args.length; i < len; ++i) {
76154                 me.addProvider(args[i]);
76155             }
76156             return;
76157         }
76158
76159         // if provider has not already been instantiated
76160         if (!provider.isProvider) {
76161             provider = Ext.create('direct.' + provider.type + 'provider', provider);
76162         }
76163         me.providers.add(provider);
76164         provider.on('data', me.onProviderData, me);
76165
76166
76167         if (!provider.isConnected()) {
76168             provider.connect();
76169         }
76170
76171         return provider;
76172     },
76173
76174     /**
76175      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
76176      * provider is {@link #addProvider added}.
76177      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
76178      */
76179     getProvider : function(id){
76180         return id.isProvider ? id : this.providers.get(id);
76181     },
76182
76183     /**
76184      * Removes the provider.
76185      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
76186      * @return {Ext.direct.Provider} The provider, null if not found.
76187      */
76188     removeProvider : function(provider){
76189         var me = this,
76190             providers = me.providers;
76191
76192         provider = provider.isProvider ? provider : providers.get(provider);
76193
76194         if (provider) {
76195             provider.un('data', me.onProviderData, me);
76196             providers.remove(provider);
76197             return provider;
76198         }
76199         return null;
76200     },
76201
76202     /**
76203      * Adds a transaction to the manager.
76204      * @private
76205      * @param {Ext.direct.Transaction} transaction The transaction to add
76206      * @return {Ext.direct.Transaction} transaction
76207      */
76208     addTransaction: function(transaction){
76209         this.transactions.add(transaction);
76210         return transaction;
76211     },
76212
76213     /**
76214      * Removes a transaction from the manager.
76215      * @private
76216      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
76217      * @return {Ext.direct.Transaction} transaction
76218      */
76219     removeTransaction: function(transaction){
76220         transaction = this.getTransaction(transaction);
76221         this.transactions.remove(transaction);
76222         return transaction;
76223     },
76224
76225     /**
76226      * Gets a transaction
76227      * @private
76228      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
76229      * @return {Ext.direct.Transaction}
76230      */
76231     getTransaction: function(transaction){
76232         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
76233     },
76234
76235     onProviderData : function(provider, event){
76236         var me = this,
76237             i = 0,
76238             len;
76239
76240         if (Ext.isArray(event)) {
76241             for (len = event.length; i < len; ++i) {
76242                 me.onProviderData(provider, event[i]);
76243             }
76244             return;
76245         }
76246         if (event.name && event.name != 'event' && event.name != 'exception') {
76247             me.fireEvent(event.name, event);
76248         } else if (event.status === false) {
76249             me.fireEvent('exception', event);
76250         }
76251         me.fireEvent('event', event, provider);
76252     }
76253 }, function(){
76254     // Backwards compatibility
76255     Ext.Direct = Ext.direct.Manager;
76256 });
76257
76258 /**
76259  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
76260  * request is made, the transport mechanism is handed off to the appropriate
76261  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
76262  *
76263  * # Specifying the function
76264  *
76265  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
76266  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
76267  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
76268  * allows you to specify a different remoting method for each CRUD action.
76269  *
76270  * # Parameters
76271  *
76272  * This proxy provides options to help configure which parameters will be sent to the server.
76273  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
76274  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
76275  * the remoting method parameters are passed.
76276  *
76277  * # Example Usage
76278  *
76279  *     Ext.define('User', {
76280  *         extend: 'Ext.data.Model',
76281  *         fields: ['firstName', 'lastName'],
76282  *         proxy: {
76283  *             type: 'direct',
76284  *             directFn: MyApp.getUsers,
76285  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
76286  *         }
76287  *     });
76288  *     User.load(1);
76289  */
76290 Ext.define('Ext.data.proxy.Direct', {
76291     /* Begin Definitions */
76292
76293     extend: 'Ext.data.proxy.Server',
76294     alternateClassName: 'Ext.data.DirectProxy',
76295
76296     alias: 'proxy.direct',
76297
76298     requires: ['Ext.direct.Manager'],
76299
76300     /* End Definitions */
76301
76302     /**
76303      * @cfg {String/String[]} paramOrder
76304      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
76305      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
76306      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
76307      * acceptable:
76308      *
76309      *     paramOrder: ['param1','param2','param3']
76310      *     paramOrder: 'param1 param2 param3'
76311      *     paramOrder: 'param1,param2,param3'
76312      *     paramOrder: 'param1|param2|param'
76313      */
76314     paramOrder: undefined,
76315
76316     /**
76317      * @cfg {Boolean} paramsAsHash
76318      * Send parameters as a collection of named arguments.
76319      * Providing a {@link #paramOrder} nullifies this configuration.
76320      */
76321     paramsAsHash: true,
76322
76323     /**
76324      * @cfg {Function} directFn
76325      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
76326      * for Store's which will not implement a full CRUD api.
76327      */
76328     directFn : undefined,
76329
76330     /**
76331      * @cfg {Object} api
76332      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
76333      * function call.
76334      */
76335
76336     /**
76337      * @cfg {Object} extraParams
76338      * Extra parameters that will be included on every read request. Individual requests with params
76339      * of the same name will override these params when they are in conflict.
76340      */
76341
76342     // private
76343     paramOrderRe: /[\s,|]/,
76344
76345     constructor: function(config){
76346         var me = this;
76347
76348         Ext.apply(me, config);
76349         if (Ext.isString(me.paramOrder)) {
76350             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
76351         }
76352         me.callParent(arguments);
76353     },
76354
76355     doRequest: function(operation, callback, scope) {
76356         var me = this,
76357             writer = me.getWriter(),
76358             request = me.buildRequest(operation, callback, scope),
76359             fn = me.api[request.action]  || me.directFn,
76360             args = [],
76361             params = request.params,
76362             paramOrder = me.paramOrder,
76363             method,
76364             i = 0,
76365             len;
76366
76367
76368         if (operation.allowWrite()) {
76369             request = writer.write(request);
76370         }
76371
76372         if (operation.action == 'read') {
76373             // We need to pass params
76374             method = fn.directCfg.method;
76375
76376             if (method.ordered) {
76377                 if (method.len > 0) {
76378                     if (paramOrder) {
76379                         for (len = paramOrder.length; i < len; ++i) {
76380                             args.push(params[paramOrder[i]]);
76381                         }
76382                     } else if (me.paramsAsHash) {
76383                         args.push(params);
76384                     }
76385                 }
76386             } else {
76387                 args.push(params);
76388             }
76389         } else {
76390             args.push(request.jsonData);
76391         }
76392
76393         Ext.apply(request, {
76394             args: args,
76395             directFn: fn
76396         });
76397         args.push(me.createRequestCallback(request, operation, callback, scope), me);
76398         fn.apply(window, args);
76399     },
76400
76401     /*
76402      * Inherit docs. We don't apply any encoding here because
76403      * all of the direct requests go out as jsonData
76404      */
76405     applyEncoding: function(value){
76406         return value;
76407     },
76408
76409     createRequestCallback: function(request, operation, callback, scope){
76410         var me = this;
76411
76412         return function(data, event){
76413             me.processResponse(event.status, operation, request, event, callback, scope);
76414         };
76415     },
76416
76417     // inherit docs
76418     extractResponseData: function(response){
76419         return Ext.isDefined(response.result) ? response.result : response.data;
76420     },
76421
76422     // inherit docs
76423     setException: function(operation, response) {
76424         operation.setException(response.message);
76425     },
76426
76427     // inherit docs
76428     buildUrl: function(){
76429         return '';
76430     }
76431 });
76432
76433 /**
76434  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
76435  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
76436  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
76437  * {@link Ext.data.Store} configured as needed.
76438  *
76439  * **Note:** Although they are not listed, this class inherits all of the config options of:
76440  *
76441  * - **{@link Ext.data.Store Store}**
76442  *
76443  * - **{@link Ext.data.reader.Json JsonReader}**
76444  *
76445  *   - **{@link Ext.data.reader.Json#root root}**
76446  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
76447  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
76448  *
76449  * - **{@link Ext.data.proxy.Direct DirectProxy}**
76450  *
76451  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
76452  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
76453  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
76454  *
76455  */
76456 Ext.define('Ext.data.DirectStore', {
76457     /* Begin Definitions */
76458     
76459     extend: 'Ext.data.Store',
76460     
76461     alias: 'store.direct',
76462     
76463     requires: ['Ext.data.proxy.Direct'],
76464    
76465     /* End Definitions */
76466
76467     constructor : function(config){
76468         config = Ext.apply({}, config);
76469         if (!config.proxy) {
76470             var proxy = {
76471                 type: 'direct',
76472                 reader: {
76473                     type: 'json'
76474                 }
76475             };
76476             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
76477             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
76478             config.proxy = proxy;
76479         }
76480         this.callParent([config]);
76481     }    
76482 });
76483
76484 /**
76485  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
76486  * {@link #ordinalize ordinalizes} words. Sample usage:
76487  *
76488  *     //turning singular words into plurals
76489  *     Ext.util.Inflector.pluralize('word'); //'words'
76490  *     Ext.util.Inflector.pluralize('person'); //'people'
76491  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
76492  *
76493  *     //turning plurals into singulars
76494  *     Ext.util.Inflector.singularize('words'); //'word'
76495  *     Ext.util.Inflector.singularize('people'); //'person'
76496  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
76497  *
76498  *     //ordinalizing numbers
76499  *     Ext.util.Inflector.ordinalize(11); //"11th"
76500  *     Ext.util.Inflector.ordinalize(21); //"21th"
76501  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
76502  *
76503  * # Customization
76504  *
76505  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
76506  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
76507  * Here is how we might add a rule that pluralizes "ox" to "oxen":
76508  *
76509  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
76510  *
76511  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
76512  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
76513  * how we could add the inverse rule:
76514  *
76515  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
76516  *
76517  * Note that the ox/oxen rules are present by default.
76518  */
76519 Ext.define('Ext.util.Inflector', {
76520
76521     /* Begin Definitions */
76522
76523     singleton: true,
76524
76525     /* End Definitions */
76526
76527     /**
76528      * @private
76529      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
76530      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
76531      * part of the regular expression. This is managed by the {@link #plural} method.
76532      * @property {Array} plurals
76533      */
76534     plurals: [
76535         [(/(quiz)$/i),                "$1zes"  ],
76536         [(/^(ox)$/i),                 "$1en"   ],
76537         [(/([m|l])ouse$/i),           "$1ice"  ],
76538         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
76539         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
76540         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
76541         [(/(hive)$/i),                "$1s"    ],
76542         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
76543         [(/sis$/i),                   "ses"    ],
76544         [(/([ti])um$/i),              "$1a"    ],
76545         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
76546         [(/(bu)s$/i),                 "$1ses"  ],
76547         [(/(alias|status|sex)$/i),    "$1es"   ],
76548         [(/(octop|vir)us$/i),         "$1i"    ],
76549         [(/(ax|test)is$/i),           "$1es"   ],
76550         [(/^person$/),                "people" ],
76551         [(/^man$/),                   "men"    ],
76552         [(/^(child)$/),               "$1ren"  ],
76553         [(/s$/i),                     "s"      ],
76554         [(/$/),                       "s"      ]
76555     ],
76556
76557     /**
76558      * @private
76559      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
76560      * regular expression that matches the plural form of a word, the second must be a String that replaces the
76561      * matched part of the regular expression. This is managed by the {@link #singular} method.
76562      * @property {Array} singulars
76563      */
76564     singulars: [
76565       [(/(quiz)zes$/i),                                                    "$1"     ],
76566       [(/(matr)ices$/i),                                                   "$1ix"   ],
76567       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
76568       [(/^(ox)en/i),                                                       "$1"     ],
76569       [(/(alias|status)es$/i),                                             "$1"     ],
76570       [(/(octop|vir)i$/i),                                                 "$1us"   ],
76571       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
76572       [(/(shoe)s$/i),                                                      "$1"     ],
76573       [(/(o)es$/i),                                                        "$1"     ],
76574       [(/(bus)es$/i),                                                      "$1"     ],
76575       [(/([m|l])ice$/i),                                                   "$1ouse" ],
76576       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
76577       [(/(m)ovies$/i),                                                     "$1ovie" ],
76578       [(/(s)eries$/i),                                                     "$1eries"],
76579       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
76580       [(/([lr])ves$/i),                                                    "$1f"    ],
76581       [(/(tive)s$/i),                                                      "$1"     ],
76582       [(/(hive)s$/i),                                                      "$1"     ],
76583       [(/([^f])ves$/i),                                                    "$1fe"   ],
76584       [(/(^analy)ses$/i),                                                  "$1sis"  ],
76585       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
76586       [(/([ti])a$/i),                                                      "$1um"   ],
76587       [(/(n)ews$/i),                                                       "$1ews"  ],
76588       [(/people$/i),                                                       "person" ],
76589       [(/s$/i),                                                            ""       ]
76590     ],
76591
76592     /**
76593      * @private
76594      * The registered uncountable words
76595      * @property {String[]} uncountable
76596      */
76597      uncountable: [
76598         "sheep",
76599         "fish",
76600         "series",
76601         "species",
76602         "money",
76603         "rice",
76604         "information",
76605         "equipment",
76606         "grass",
76607         "mud",
76608         "offspring",
76609         "deer",
76610         "means"
76611     ],
76612
76613     /**
76614      * Adds a new singularization rule to the Inflector. See the intro docs for more information
76615      * @param {RegExp} matcher The matcher regex
76616      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76617      */
76618     singular: function(matcher, replacer) {
76619         this.singulars.unshift([matcher, replacer]);
76620     },
76621
76622     /**
76623      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
76624      * @param {RegExp} matcher The matcher regex
76625      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76626      */
76627     plural: function(matcher, replacer) {
76628         this.plurals.unshift([matcher, replacer]);
76629     },
76630
76631     /**
76632      * Removes all registered singularization rules
76633      */
76634     clearSingulars: function() {
76635         this.singulars = [];
76636     },
76637
76638     /**
76639      * Removes all registered pluralization rules
76640      */
76641     clearPlurals: function() {
76642         this.plurals = [];
76643     },
76644
76645     /**
76646      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
76647      * @param {String} word The word to test
76648      * @return {Boolean} True if the word is transnumeral
76649      */
76650     isTransnumeral: function(word) {
76651         return Ext.Array.indexOf(this.uncountable, word) != -1;
76652     },
76653
76654     /**
76655      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
76656      * @param {String} word The word to pluralize
76657      * @return {String} The pluralized form of the word
76658      */
76659     pluralize: function(word) {
76660         if (this.isTransnumeral(word)) {
76661             return word;
76662         }
76663
76664         var plurals = this.plurals,
76665             length  = plurals.length,
76666             tuple, regex, i;
76667
76668         for (i = 0; i < length; i++) {
76669             tuple = plurals[i];
76670             regex = tuple[0];
76671
76672             if (regex == word || (regex.test && regex.test(word))) {
76673                 return word.replace(regex, tuple[1]);
76674             }
76675         }
76676
76677         return word;
76678     },
76679
76680     /**
76681      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
76682      * @param {String} word The word to singularize
76683      * @return {String} The singularized form of the word
76684      */
76685     singularize: function(word) {
76686         if (this.isTransnumeral(word)) {
76687             return word;
76688         }
76689
76690         var singulars = this.singulars,
76691             length    = singulars.length,
76692             tuple, regex, i;
76693
76694         for (i = 0; i < length; i++) {
76695             tuple = singulars[i];
76696             regex = tuple[0];
76697
76698             if (regex == word || (regex.test && regex.test(word))) {
76699                 return word.replace(regex, tuple[1]);
76700             }
76701         }
76702
76703         return word;
76704     },
76705
76706     /**
76707      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
76708      * package
76709      * @param {String} word The word to classify
76710      * @return {String} The classified version of the word
76711      */
76712     classify: function(word) {
76713         return Ext.String.capitalize(this.singularize(word));
76714     },
76715
76716     /**
76717      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
76718      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
76719      * @param {Number} number The number to ordinalize
76720      * @return {String} The ordinalized number
76721      */
76722     ordinalize: function(number) {
76723         var parsed = parseInt(number, 10),
76724             mod10  = parsed % 10,
76725             mod100 = parsed % 100;
76726
76727         //11 through 13 are a special case
76728         if (11 <= mod100 && mod100 <= 13) {
76729             return number + "th";
76730         } else {
76731             switch(mod10) {
76732                 case 1 : return number + "st";
76733                 case 2 : return number + "nd";
76734                 case 3 : return number + "rd";
76735                 default: return number + "th";
76736             }
76737         }
76738     }
76739 }, function() {
76740     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
76741     var irregulars = {
76742             alumnus: 'alumni',
76743             cactus : 'cacti',
76744             focus  : 'foci',
76745             nucleus: 'nuclei',
76746             radius: 'radii',
76747             stimulus: 'stimuli',
76748             ellipsis: 'ellipses',
76749             paralysis: 'paralyses',
76750             oasis: 'oases',
76751             appendix: 'appendices',
76752             index: 'indexes',
76753             beau: 'beaux',
76754             bureau: 'bureaux',
76755             tableau: 'tableaux',
76756             woman: 'women',
76757             child: 'children',
76758             man: 'men',
76759             corpus:     'corpora',
76760             criterion: 'criteria',
76761             curriculum: 'curricula',
76762             genus: 'genera',
76763             memorandum: 'memoranda',
76764             phenomenon: 'phenomena',
76765             foot: 'feet',
76766             goose: 'geese',
76767             tooth: 'teeth',
76768             antenna: 'antennae',
76769             formula: 'formulae',
76770             nebula: 'nebulae',
76771             vertebra: 'vertebrae',
76772             vita: 'vitae'
76773         },
76774         singular;
76775
76776     for (singular in irregulars) {
76777         this.plural(singular, irregulars[singular]);
76778         this.singular(irregulars[singular], singular);
76779     }
76780 });
76781 /**
76782  * @author Ed Spencer
76783  * @class Ext.data.HasManyAssociation
76784  * @extends Ext.data.Association
76785  * 
76786  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
76787  * 
76788 <pre><code>
76789 Ext.define('Product', {
76790     extend: 'Ext.data.Model',
76791     fields: [
76792         {name: 'id',      type: 'int'},
76793         {name: 'user_id', type: 'int'},
76794         {name: 'name',    type: 'string'}
76795     ]
76796 });
76797
76798 Ext.define('User', {
76799     extend: 'Ext.data.Model',
76800     fields: [
76801         {name: 'id',   type: 'int'},
76802         {name: 'name', type: 'string'}
76803     ],
76804     // we can use the hasMany shortcut on the model to create a hasMany association
76805     hasMany: {model: 'Product', name: 'products'}
76806 });
76807 </pre></code>
76808
76809  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
76810  * us a new function on every User instance, in this case the function is called 'products' because that is the name
76811  * we specified in the association configuration above.</p>
76812  * 
76813  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
76814  * only Products for the given model instance:</p>
76815  * 
76816 <pre><code>
76817 //first, we load up a User with id of 1
76818 var user = Ext.create('User', {id: 1, name: 'Ed'});
76819
76820 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
76821 //the created store is automatically scoped to the set of Products for the User with id of 1
76822 var products = user.products();
76823
76824 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
76825 products.add({
76826     name: 'Another Product'
76827 });
76828
76829 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
76830 products.sync();
76831 </code></pre>
76832  * 
76833  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
76834  * though calling products() a second time returns the same store instance.</p>
76835  * 
76836  * <p><u>Custom filtering</u></p>
76837  * 
76838  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
76839  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
76840  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
76841  * 
76842  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
76843  * have models for Search and Tweet:</p>
76844  * 
76845 <pre><code>
76846 Ext.define('Search', {
76847     extend: 'Ext.data.Model',
76848     fields: [
76849         'id', 'query'
76850     ],
76851
76852     hasMany: {
76853         model: 'Tweet',
76854         name : 'tweets',
76855         filterProperty: 'query'
76856     }
76857 });
76858
76859 Ext.define('Tweet', {
76860     extend: 'Ext.data.Model',
76861     fields: [
76862         'id', 'text', 'from_user'
76863     ]
76864 });
76865
76866 //returns a Store filtered by the filterProperty
76867 var store = new Search({query: 'Sencha Touch'}).tweets();
76868 </code></pre>
76869  * 
76870  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
76871  * equivalent to this:</p>
76872  * 
76873 <pre><code>
76874 var store = Ext.create('Ext.data.Store', {
76875     model: 'Tweet',
76876     filters: [
76877         {
76878             property: 'query',
76879             value   : 'Sencha Touch'
76880         }
76881     ]
76882 });
76883 </code></pre>
76884  */
76885 Ext.define('Ext.data.HasManyAssociation', {
76886     extend: 'Ext.data.Association',
76887     requires: ['Ext.util.Inflector'],
76888
76889     alias: 'association.hasmany',
76890
76891     /**
76892      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
76893      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
76894      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
76895      * the store is automatically filtered so that only records with a matching foreign key are included in the 
76896      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
76897      * <pre><code>
76898 Ext.define('Group', {
76899     extend: 'Ext.data.Model',
76900     fields: ['id', 'name'],
76901     hasMany: 'User'
76902 });
76903
76904 Ext.define('User', {
76905     extend: 'Ext.data.Model',
76906     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
76907     belongsTo: 'Group'
76908 });
76909      * </code></pre>
76910      */
76911     
76912     /**
76913      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
76914      * If not specified, the pluralized name of the child model is used.
76915      * <pre><code>
76916 // This will create a users() method on any Group model instance
76917 Ext.define('Group', {
76918     extend: 'Ext.data.Model',
76919     fields: ['id', 'name'],
76920     hasMany: 'User'
76921 });
76922 var group = new Group();
76923 console.log(group.users());
76924
76925 // The method to retrieve the users will now be getUserList
76926 Ext.define('Group', {
76927     extend: 'Ext.data.Model',
76928     fields: ['id', 'name'],
76929     hasMany: {model: 'User', name: 'getUserList'}
76930 });
76931 var group = new Group();
76932 console.log(group.getUserList());
76933      * </code></pre>
76934      */
76935     
76936     /**
76937      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
76938      * undefined.
76939      */
76940     
76941     /**
76942      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
76943      * this is not set, a filter is automatically created which filters the association based on the configured 
76944      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
76945      */
76946     
76947     /**
76948      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
76949      * Defaults to <tt>false</tt>.
76950      */
76951     
76952     /**
76953      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
76954      * Use 'hasMany' to create a HasManyAssocation
76955      * <pre><code>
76956 associations: [{
76957     type: 'hasMany',
76958     model: 'User'
76959 }]
76960      * </code></pre>
76961      */
76962     
76963     constructor: function(config) {
76964         var me = this,
76965             ownerProto,
76966             name;
76967             
76968         me.callParent(arguments);
76969         
76970         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
76971         
76972         ownerProto = me.ownerModel.prototype;
76973         name = me.name;
76974         
76975         Ext.applyIf(me, {
76976             storeName : name + "Store",
76977             foreignKey: me.ownerName.toLowerCase() + "_id"
76978         });
76979         
76980         ownerProto[name] = me.createStore();
76981     },
76982     
76983     /**
76984      * @private
76985      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
76986      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
76987      * returns a Store configured to return the filtered set of a single Group's Users.
76988      * @return {Function} The store-generating function
76989      */
76990     createStore: function() {
76991         var that            = this,
76992             associatedModel = that.associatedModel,
76993             storeName       = that.storeName,
76994             foreignKey      = that.foreignKey,
76995             primaryKey      = that.primaryKey,
76996             filterProperty  = that.filterProperty,
76997             autoLoad        = that.autoLoad,
76998             storeConfig     = that.storeConfig || {};
76999         
77000         return function() {
77001             var me = this,
77002                 config, filter,
77003                 modelDefaults = {};
77004                 
77005             if (me[storeName] === undefined) {
77006                 if (filterProperty) {
77007                     filter = {
77008                         property  : filterProperty,
77009                         value     : me.get(filterProperty),
77010                         exactMatch: true
77011                     };
77012                 } else {
77013                     filter = {
77014                         property  : foreignKey,
77015                         value     : me.get(primaryKey),
77016                         exactMatch: true
77017                     };
77018                 }
77019                 
77020                 modelDefaults[foreignKey] = me.get(primaryKey);
77021                 
77022                 config = Ext.apply({}, storeConfig, {
77023                     model        : associatedModel,
77024                     filters      : [filter],
77025                     remoteFilter : false,
77026                     modelDefaults: modelDefaults
77027                 });
77028                 
77029                 me[storeName] = Ext.create('Ext.data.Store', config);
77030                 if (autoLoad) {
77031                     me[storeName].load();
77032                 }
77033             }
77034             
77035             return me[storeName];
77036         };
77037     },
77038     
77039     /**
77040      * Read associated data
77041      * @private
77042      * @param {Ext.data.Model} record The record we're writing to
77043      * @param {Ext.data.reader.Reader} reader The reader for the associated model
77044      * @param {Object} associationData The raw associated data
77045      */
77046     read: function(record, reader, associationData){
77047         var store = record[this.name](),
77048             inverse;
77049     
77050         store.add(reader.read(associationData).records);
77051     
77052         //now that we've added the related records to the hasMany association, set the inverse belongsTo
77053         //association on each of them if it exists
77054         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
77055             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
77056         });
77057     
77058         //if the inverse association was found, set it now on each record we've just created
77059         if (inverse) {
77060             store.data.each(function(associatedRecord){
77061                 associatedRecord[inverse.instanceName] = record;
77062             });
77063         }
77064     }
77065 });
77066 /**
77067  * @class Ext.data.JsonP
77068  * @singleton
77069  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
77070  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
77071  */
77072 Ext.define('Ext.data.JsonP', {
77073
77074     /* Begin Definitions */
77075
77076     singleton: true,
77077
77078     statics: {
77079         requestCount: 0,
77080         requests: {}
77081     },
77082
77083     /* End Definitions */
77084
77085     /**
77086      * @property timeout
77087      * @type Number
77088      * A default timeout for any JsonP requests. If the request has not completed in this time the
77089      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
77090      */
77091     timeout: 30000,
77092
77093     /**
77094      * @property disableCaching
77095      * @type Boolean
77096      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
77097      */
77098     disableCaching: true,
77099
77100     /**
77101      * @property disableCachingParam
77102      * @type String
77103      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
77104      */
77105     disableCachingParam: '_dc',
77106
77107     /**
77108      * @property callbackKey
77109      * @type String
77110      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
77111      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
77112      * url?callback=Ext.data.JsonP.callback1
77113      */
77114     callbackKey: 'callback',
77115
77116     /**
77117      * Makes a JSONP request.
77118      * @param {Object} options An object which may contain the following properties. Note that options will
77119      * take priority over any defaults that are specified in the class.
77120      * <ul>
77121      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
77122      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
77123      * key value pairs that will be sent along with the request.</div></li>
77124      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
77125      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
77126      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77127      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77128      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
77129      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
77130      * in each JsonP request that you make.</div></li>
77131      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
77132      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
77133      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
77134      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
77135      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
77136      * completes, whether it is a success or failure.</div></li>
77137      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
77138      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
77139      * </ul>
77140      * @return {Object} request An object containing the request details.
77141      */
77142     request: function(options){
77143         options = Ext.apply({}, options);
77144
77145
77146         var me = this,
77147             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
77148             cacheParam = options.disableCachingParam || me.disableCachingParam,
77149             id = ++me.statics().requestCount,
77150             callbackName = options.callbackName || 'callback' + id,
77151             callbackKey = options.callbackKey || me.callbackKey,
77152             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
77153             params = Ext.apply({}, options.params),
77154             url = options.url,
77155             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
77156             request,
77157             script;
77158
77159         params[callbackKey] = name + '.data.JsonP.' + callbackName;
77160         if (disableCaching) {
77161             params[cacheParam] = new Date().getTime();
77162         }
77163
77164         script = me.createScript(url, params);
77165
77166         me.statics().requests[id] = request = {
77167             url: url,
77168             params: params,
77169             script: script,
77170             id: id,
77171             scope: options.scope,
77172             success: options.success,
77173             failure: options.failure,
77174             callback: options.callback,
77175             callbackName: callbackName
77176         };
77177
77178         if (timeout > 0) {
77179             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
77180         }
77181
77182         me.setupErrorHandling(request);
77183         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
77184         Ext.getHead().appendChild(script);
77185         return request;
77186     },
77187
77188     /**
77189      * Abort a request. If the request parameter is not specified all open requests will
77190      * be aborted.
77191      * @param {Object/String} request (Optional) The request to abort
77192      */
77193     abort: function(request){
77194         var requests = this.statics().requests,
77195             key;
77196
77197         if (request) {
77198             if (!request.id) {
77199                 request = requests[request];
77200             }
77201             this.abort(request);
77202         } else {
77203             for (key in requests) {
77204                 if (requests.hasOwnProperty(key)) {
77205                     this.abort(requests[key]);
77206                 }
77207             }
77208         }
77209     },
77210
77211     /**
77212      * Sets up error handling for the script
77213      * @private
77214      * @param {Object} request The request
77215      */
77216     setupErrorHandling: function(request){
77217         request.script.onerror = Ext.bind(this.handleError, this, [request]);
77218     },
77219
77220     /**
77221      * Handles any aborts when loading the script
77222      * @private
77223      * @param {Object} request The request
77224      */
77225     handleAbort: function(request){
77226         request.errorType = 'abort';
77227         this.handleResponse(null, request);
77228     },
77229
77230     /**
77231      * Handles any script errors when loading the script
77232      * @private
77233      * @param {Object} request The request
77234      */
77235     handleError: function(request){
77236         request.errorType = 'error';
77237         this.handleResponse(null, request);
77238     },
77239
77240     /**
77241      * Cleans up anu script handling errors
77242      * @private
77243      * @param {Object} request The request
77244      */
77245     cleanupErrorHandling: function(request){
77246         request.script.onerror = null;
77247     },
77248
77249     /**
77250      * Handle any script timeouts
77251      * @private
77252      * @param {Object} request The request
77253      */
77254     handleTimeout: function(request){
77255         request.errorType = 'timeout';
77256         this.handleResponse(null, request);
77257     },
77258
77259     /**
77260      * Handle a successful response
77261      * @private
77262      * @param {Object} result The result from the request
77263      * @param {Object} request The request
77264      */
77265     handleResponse: function(result, request){
77266
77267         var success = true;
77268
77269         if (request.timeout) {
77270             clearTimeout(request.timeout);
77271         }
77272         delete this[request.callbackName];
77273         delete this.statics()[request.id];
77274         this.cleanupErrorHandling(request);
77275         Ext.fly(request.script).remove();
77276
77277         if (request.errorType) {
77278             success = false;
77279             Ext.callback(request.failure, request.scope, [request.errorType]);
77280         } else {
77281             Ext.callback(request.success, request.scope, [result]);
77282         }
77283         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
77284     },
77285
77286     /**
77287      * Create the script tag
77288      * @private
77289      * @param {String} url The url of the request
77290      * @param {Object} params Any extra params to be sent
77291      */
77292     createScript: function(url, params) {
77293         var script = document.createElement('script');
77294         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
77295         script.setAttribute("async", true);
77296         script.setAttribute("type", "text/javascript");
77297         return script;
77298     }
77299 });
77300
77301 /**
77302  * @class Ext.data.JsonPStore
77303  * @extends Ext.data.Store
77304  * @private
77305  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
77306  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
77307  * <p>A store configuration would be something like:<pre><code>
77308 var store = new Ext.data.JsonPStore({
77309     // store configs
77310     autoDestroy: true,
77311     storeId: 'myStore',
77312
77313     // proxy configs
77314     url: 'get-images.php',
77315
77316     // reader configs
77317     root: 'images',
77318     idProperty: 'name',
77319     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
77320 });
77321  * </code></pre></p>
77322  * <p>This store is configured to consume a returned object of the form:<pre><code>
77323 stcCallback({
77324     images: [
77325         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
77326         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
77327     ]
77328 })
77329  * </code></pre>
77330  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
77331  * for details of how this works.</p>
77332  * An object literal of this form could also be used as the {@link #data} config option.</p>
77333  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
77334  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
77335  * @xtype jsonpstore
77336  */
77337 Ext.define('Ext.data.JsonPStore', {
77338     extend: 'Ext.data.Store',
77339     alias : 'store.jsonp',
77340
77341     /**
77342      * @cfg {Ext.data.DataReader} reader @hide
77343      */
77344     constructor: function(config) {
77345         this.callParent(Ext.apply(config, {
77346             reader: Ext.create('Ext.data.reader.Json', config),
77347             proxy : Ext.create('Ext.data.proxy.JsonP', config)
77348         }));
77349     }
77350 });
77351
77352 /**
77353  * This class is used as a set of methods that are applied to the prototype of a
77354  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
77355  * will have all of the tree related methods available on the model. In general this class will
77356  * not be used directly by the developer. This class also creates extra fields on the model if
77357  * they do not exist, to help maintain the tree state and UI. These fields are documented as
77358  * config options.
77359  */
77360 Ext.define('Ext.data.NodeInterface', {
77361     requires: ['Ext.data.Field'],
77362
77363     /**
77364      * @cfg {String} parentId
77365      * ID of parent node.
77366      */
77367
77368     /**
77369      * @cfg {Number} index
77370      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
77371      * index will be 2.
77372      */
77373
77374     /**
77375      * @cfg {Number} depth
77376      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
77377      */
77378
77379     /**
77380      * @cfg {Boolean} [expanded=false]
77381      * True if the node is expanded.
77382      */
77383
77384     /**
77385      * @cfg {Boolean} [expandable=false]
77386      * Set to true to allow for expanding/collapsing of this node.
77387      */
77388
77389     /**
77390      * @cfg {Boolean} [checked=null]
77391      * Set to true or false to show a checkbox alongside this node.
77392      */
77393
77394     /**
77395      * @cfg {Boolean} [leaf=false]
77396      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
77397      * rendered for this node.
77398      */
77399
77400     /**
77401      * @cfg {String} cls
77402      * CSS class to apply for this node.
77403      */
77404
77405     /**
77406      * @cfg {String} iconCls
77407      * CSS class to apply for this node's icon.
77408      */
77409
77410     /**
77411      * @cfg {String} icon
77412      * URL for this node's icon.
77413      */
77414
77415     /**
77416      * @cfg {Boolean} root
77417      * True if this is the root node.
77418      */
77419
77420     /**
77421      * @cfg {Boolean} isLast
77422      * True if this is the last node.
77423      */
77424
77425     /**
77426      * @cfg {Boolean} isFirst
77427      * True if this is the first node.
77428      */
77429
77430     /**
77431      * @cfg {Boolean} [allowDrop=true]
77432      * Set to false to deny dropping on this node.
77433      */
77434
77435     /**
77436      * @cfg {Boolean} [allowDrag=true]
77437      * Set to false to deny dragging of this node.
77438      */
77439
77440     /**
77441      * @cfg {Boolean} [loaded=false]
77442      * True if the node has finished loading.
77443      */
77444
77445     /**
77446      * @cfg {Boolean} [loading=false]
77447      * True if the node is currently loading.
77448      */
77449
77450     /**
77451      * @cfg {String} href
77452      * An URL for a link that's created when this config is specified.
77453      */
77454
77455     /**
77456      * @cfg {String} hrefTarget
77457      * Target for link. Only applicable when {@link #href} also specified.
77458      */
77459
77460     /**
77461      * @cfg {String} qtip
77462      * Tooltip text to show on this node.
77463      */
77464
77465     /**
77466      * @cfg {String} qtitle
77467      * Tooltip title.
77468      */
77469
77470     /**
77471      * @cfg {String} text
77472      * The text for to show on node label.
77473      */
77474
77475     /**
77476      * @cfg {Ext.data.NodeInterface[]} children
77477      * Array of child nodes.
77478      */
77479
77480
77481     /**
77482      * @property nextSibling
77483      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
77484      */
77485
77486     /**
77487      * @property previousSibling
77488      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
77489      */
77490
77491     /**
77492      * @property parentNode
77493      * A reference to this node's parent node. `null` if this node is the root node.
77494      */
77495
77496     /**
77497      * @property lastChild
77498      * A reference to this node's last child node. `null` if this node has no children.
77499      */
77500
77501     /**
77502      * @property firstChild
77503      * A reference to this node's first child node. `null` if this node has no children.
77504      */
77505
77506     /**
77507      * @property childNodes
77508      * An array of this nodes children.  Array will be empty if this node has no chidren.
77509      */
77510
77511     statics: {
77512         /**
77513          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
77514          * This adds a set of methods, new events, new properties and new fields on every Record
77515          * with the same Model as the passed Record.
77516          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
77517          * @static
77518          */
77519         decorate: function(record) {
77520             if (!record.isNode) {
77521                 // Apply the methods and fields to the prototype
77522                 // @TODO: clean this up to use proper class system stuff
77523                 var mgr = Ext.ModelManager,
77524                     modelName = record.modelName,
77525                     modelClass = mgr.getModel(modelName),
77526                     idName = modelClass.prototype.idProperty,
77527                     newFields = [],
77528                     i, newField, len;
77529
77530                 // Start by adding the NodeInterface methods to the Model's prototype
77531                 modelClass.override(this.getPrototypeBody());
77532                 newFields = this.applyFields(modelClass, [
77533                     {name: idName,       type: 'string',  defaultValue: null},
77534                     {name: 'parentId',   type: 'string',  defaultValue: null},
77535                     {name: 'index',      type: 'int',     defaultValue: null},
77536                     {name: 'depth',      type: 'int',     defaultValue: 0},
77537                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
77538                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
77539                     {name: 'checked',    type: 'auto',    defaultValue: null},
77540                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
77541                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
77542                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
77543                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
77544                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
77545                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
77546                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
77547                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
77548                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
77549                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
77550                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
77551                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
77552                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
77553                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
77554                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
77555                 ]);
77556
77557                 len = newFields.length;
77558                 // Set default values
77559                 for (i = 0; i < len; ++i) {
77560                     newField = newFields[i];
77561                     if (record.get(newField.name) === undefined) {
77562                         record.data[newField.name] = newField.defaultValue;
77563                     }
77564                 }
77565             }
77566
77567             Ext.applyIf(record, {
77568                 firstChild: null,
77569                 lastChild: null,
77570                 parentNode: null,
77571                 previousSibling: null,
77572                 nextSibling: null,
77573                 childNodes: []
77574             });
77575             // Commit any fields so the record doesn't show as dirty initially
77576             record.commit(true);
77577
77578             record.enableBubble([
77579                 /**
77580                  * @event append
77581                  * Fires when a new child node is appended
77582                  * @param {Ext.data.NodeInterface} this This node
77583                  * @param {Ext.data.NodeInterface} node The newly appended node
77584                  * @param {Number} index The index of the newly appended node
77585                  */
77586                 "append",
77587
77588                 /**
77589                  * @event remove
77590                  * Fires when a child node is removed
77591                  * @param {Ext.data.NodeInterface} this This node
77592                  * @param {Ext.data.NodeInterface} node The removed node
77593                  */
77594                 "remove",
77595
77596                 /**
77597                  * @event move
77598                  * Fires when this node is moved to a new location in the tree
77599                  * @param {Ext.data.NodeInterface} this This node
77600                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
77601                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
77602                  * @param {Number} index The index it was moved to
77603                  */
77604                 "move",
77605
77606                 /**
77607                  * @event insert
77608                  * Fires when a new child node is inserted.
77609                  * @param {Ext.data.NodeInterface} this This node
77610                  * @param {Ext.data.NodeInterface} node The child node inserted
77611                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
77612                  */
77613                 "insert",
77614
77615                 /**
77616                  * @event beforeappend
77617                  * Fires before a new child is appended, return false to cancel the append.
77618                  * @param {Ext.data.NodeInterface} this This node
77619                  * @param {Ext.data.NodeInterface} node The child node to be appended
77620                  */
77621                 "beforeappend",
77622
77623                 /**
77624                  * @event beforeremove
77625                  * Fires before a child is removed, return false to cancel the remove.
77626                  * @param {Ext.data.NodeInterface} this This node
77627                  * @param {Ext.data.NodeInterface} node The child node to be removed
77628                  */
77629                 "beforeremove",
77630
77631                 /**
77632                  * @event beforemove
77633                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
77634                  * @param {Ext.data.NodeInterface} this This node
77635                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
77636                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
77637                  * @param {Number} index The index it is being moved to
77638                  */
77639                 "beforemove",
77640
77641                  /**
77642                   * @event beforeinsert
77643                   * Fires before a new child is inserted, return false to cancel the insert.
77644                   * @param {Ext.data.NodeInterface} this This node
77645                   * @param {Ext.data.NodeInterface} node The child node to be inserted
77646                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
77647                   */
77648                 "beforeinsert",
77649
77650                 /**
77651                  * @event expand
77652                  * Fires when this node is expanded.
77653                  * @param {Ext.data.NodeInterface} this The expanding node
77654                  */
77655                 "expand",
77656
77657                 /**
77658                  * @event collapse
77659                  * Fires when this node is collapsed.
77660                  * @param {Ext.data.NodeInterface} this The collapsing node
77661                  */
77662                 "collapse",
77663
77664                 /**
77665                  * @event beforeexpand
77666                  * Fires before this node is expanded.
77667                  * @param {Ext.data.NodeInterface} this The expanding node
77668                  */
77669                 "beforeexpand",
77670
77671                 /**
77672                  * @event beforecollapse
77673                  * Fires before this node is collapsed.
77674                  * @param {Ext.data.NodeInterface} this The collapsing node
77675                  */
77676                 "beforecollapse",
77677
77678                 /**
77679                  * @event sort
77680                  * Fires when this node's childNodes are sorted.
77681                  * @param {Ext.data.NodeInterface} this This node.
77682                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
77683                  */
77684                 "sort"
77685             ]);
77686
77687             return record;
77688         },
77689
77690         applyFields: function(modelClass, addFields) {
77691             var modelPrototype = modelClass.prototype,
77692                 fields = modelPrototype.fields,
77693                 keys = fields.keys,
77694                 ln = addFields.length,
77695                 addField, i, name,
77696                 newFields = [];
77697
77698             for (i = 0; i < ln; i++) {
77699                 addField = addFields[i];
77700                 if (!Ext.Array.contains(keys, addField.name)) {
77701                     addField = Ext.create('data.field', addField);
77702
77703                     newFields.push(addField);
77704                     fields.add(addField);
77705                 }
77706             }
77707
77708             return newFields;
77709         },
77710
77711         getPrototypeBody: function() {
77712             return {
77713                 isNode: true,
77714
77715                 /**
77716                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
77717                  * @return {Boolean}
77718                  */
77719                 createNode: function(node) {
77720                     if (Ext.isObject(node) && !node.isModel) {
77721                         node = Ext.ModelManager.create(node, this.modelName);
77722                     }
77723                     // Make sure the node implements the node interface
77724                     return Ext.data.NodeInterface.decorate(node);
77725                 },
77726
77727                 /**
77728                  * Returns true if this node is a leaf
77729                  * @return {Boolean}
77730                  */
77731                 isLeaf : function() {
77732                     return this.get('leaf') === true;
77733                 },
77734
77735                 /**
77736                  * Sets the first child of this node
77737                  * @private
77738                  * @param {Ext.data.NodeInterface} node
77739                  */
77740                 setFirstChild : function(node) {
77741                     this.firstChild = node;
77742                 },
77743
77744                 /**
77745                  * Sets the last child of this node
77746                  * @private
77747                  * @param {Ext.data.NodeInterface} node
77748                  */
77749                 setLastChild : function(node) {
77750                     this.lastChild = node;
77751                 },
77752
77753                 /**
77754                  * Updates general data of this node like isFirst, isLast, depth. This
77755                  * method is internally called after a node is moved. This shouldn't
77756                  * have to be called by the developer unless they are creating custom
77757                  * Tree plugins.
77758                  * @return {Boolean}
77759                  */
77760                 updateInfo: function(silent) {
77761                     var me = this,
77762                         isRoot = me.isRoot(),
77763                         parentNode = me.parentNode,
77764                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
77765                         isLast = (!parentNode ? true : parentNode.lastChild == me),
77766                         depth = 0,
77767                         parent = me,
77768                         children = me.childNodes,
77769                         len = children.length,
77770                         i = 0;
77771
77772                     while (parent.parentNode) {
77773                         ++depth;
77774                         parent = parent.parentNode;
77775                     }
77776
77777                     me.beginEdit();
77778                     me.set({
77779                         isFirst: isFirst,
77780                         isLast: isLast,
77781                         depth: depth,
77782                         index: parentNode ? parentNode.indexOf(me) : 0,
77783                         parentId: parentNode ? parentNode.getId() : null
77784                     });
77785                     me.endEdit(silent);
77786                     if (silent) {
77787                         me.commit();
77788                     }
77789
77790                     for (i = 0; i < len; i++) {
77791                         children[i].updateInfo(silent);
77792                     }
77793                 },
77794
77795                 /**
77796                  * Returns true if this node is the last child of its parent
77797                  * @return {Boolean}
77798                  */
77799                 isLast : function() {
77800                    return this.get('isLast');
77801                 },
77802
77803                 /**
77804                  * Returns true if this node is the first child of its parent
77805                  * @return {Boolean}
77806                  */
77807                 isFirst : function() {
77808                    return this.get('isFirst');
77809                 },
77810
77811                 /**
77812                  * Returns true if this node has one or more child nodes, else false.
77813                  * @return {Boolean}
77814                  */
77815                 hasChildNodes : function() {
77816                     return !this.isLeaf() && this.childNodes.length > 0;
77817                 },
77818
77819                 /**
77820                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
77821                  * node attribute is explicitly specified as true, otherwise returns false.
77822                  * @return {Boolean}
77823                  */
77824                 isExpandable : function() {
77825                     var me = this;
77826
77827                     if (me.get('expandable')) {
77828                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
77829                     }
77830                     return false;
77831                 },
77832
77833                 /**
77834                  * Inserts node(s) as the last child node of this node.
77835                  *
77836                  * If the node was previously a child node of another parent node, it will be removed from that node first.
77837                  *
77838                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
77839                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
77840                  */
77841                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
77842                     var me = this,
77843                         i, ln,
77844                         index,
77845                         oldParent,
77846                         ps;
77847
77848                     // if passed an array or multiple args do them one by one
77849                     if (Ext.isArray(node)) {
77850                         for (i = 0, ln = node.length; i < ln; i++) {
77851                             me.appendChild(node[i]);
77852                         }
77853                     } else {
77854                         // Make sure it is a record
77855                         node = me.createNode(node);
77856
77857                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
77858                             return false;
77859                         }
77860
77861                         index = me.childNodes.length;
77862                         oldParent = node.parentNode;
77863
77864                         // it's a move, make sure we move it cleanly
77865                         if (oldParent) {
77866                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
77867                                 return false;
77868                             }
77869                             oldParent.removeChild(node, null, false, true);
77870                         }
77871
77872                         index = me.childNodes.length;
77873                         if (index === 0) {
77874                             me.setFirstChild(node);
77875                         }
77876
77877                         me.childNodes.push(node);
77878                         node.parentNode = me;
77879                         node.nextSibling = null;
77880
77881                         me.setLastChild(node);
77882
77883                         ps = me.childNodes[index - 1];
77884                         if (ps) {
77885                             node.previousSibling = ps;
77886                             ps.nextSibling = node;
77887                             ps.updateInfo(suppressNodeUpdate);
77888                         } else {
77889                             node.previousSibling = null;
77890                         }
77891
77892                         node.updateInfo(suppressNodeUpdate);
77893
77894                         // As soon as we append a child to this node, we are loaded
77895                         if (!me.isLoaded()) {
77896                             me.set('loaded', true);
77897                         }
77898                         // If this node didnt have any childnodes before, update myself
77899                         else if (me.childNodes.length === 1) {
77900                             me.set('loaded', me.isLoaded());
77901                         }
77902
77903                         if (suppressEvents !== true) {
77904                             me.fireEvent("append", me, node, index);
77905
77906                             if (oldParent) {
77907                                 node.fireEvent("move", node, oldParent, me, index);
77908                             }
77909                         }
77910
77911                         return node;
77912                     }
77913                 },
77914
77915                 /**
77916                  * Returns the bubble target for this node
77917                  * @private
77918                  * @return {Object} The bubble target
77919                  */
77920                 getBubbleTarget: function() {
77921                     return this.parentNode;
77922                 },
77923
77924                 /**
77925                  * Removes a child node from this node.
77926                  * @param {Ext.data.NodeInterface} node The node to remove
77927                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
77928                  * @return {Ext.data.NodeInterface} The removed node
77929                  */
77930                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
77931                     var me = this,
77932                         index = me.indexOf(node);
77933
77934                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
77935                         return false;
77936                     }
77937
77938                     // remove it from childNodes collection
77939                     Ext.Array.erase(me.childNodes, index, 1);
77940
77941                     // update child refs
77942                     if (me.firstChild == node) {
77943                         me.setFirstChild(node.nextSibling);
77944                     }
77945                     if (me.lastChild == node) {
77946                         me.setLastChild(node.previousSibling);
77947                     }
77948
77949                     // update siblings
77950                     if (node.previousSibling) {
77951                         node.previousSibling.nextSibling = node.nextSibling;
77952                         node.previousSibling.updateInfo(suppressNodeUpdate);
77953                     }
77954                     if (node.nextSibling) {
77955                         node.nextSibling.previousSibling = node.previousSibling;
77956                         node.nextSibling.updateInfo(suppressNodeUpdate);
77957                     }
77958
77959                     if (suppressEvents !== true) {
77960                         me.fireEvent("remove", me, node);
77961                     }
77962
77963
77964                     // If this node suddenly doesnt have childnodes anymore, update myself
77965                     if (!me.childNodes.length) {
77966                         me.set('loaded', me.isLoaded());
77967                     }
77968
77969                     if (destroy) {
77970                         node.destroy(true);
77971                     } else {
77972                         node.clear();
77973                     }
77974
77975                     return node;
77976                 },
77977
77978                 /**
77979                  * Creates a copy (clone) of this Node.
77980                  * @param {String} [id] A new id, defaults to this Node's id.
77981                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
77982                  * False to copy without child Nodes.
77983                  * @return {Ext.data.NodeInterface} A copy of this Node.
77984                  */
77985                 copy: function(newId, deep) {
77986                     var me = this,
77987                         result = me.callOverridden(arguments),
77988                         len = me.childNodes ? me.childNodes.length : 0,
77989                         i;
77990
77991                     // Move child nodes across to the copy if required
77992                     if (deep) {
77993                         for (i = 0; i < len; i++) {
77994                             result.appendChild(me.childNodes[i].copy(true));
77995                         }
77996                     }
77997                     return result;
77998                 },
77999
78000                 /**
78001                  * Clears the node.
78002                  * @private
78003                  * @param {Boolean} [destroy=false] True to destroy the node.
78004                  */
78005                 clear : function(destroy) {
78006                     var me = this;
78007
78008                     // clear any references from the node
78009                     me.parentNode = me.previousSibling = me.nextSibling = null;
78010                     if (destroy) {
78011                         me.firstChild = me.lastChild = null;
78012                     }
78013                 },
78014
78015                 /**
78016                  * Destroys the node.
78017                  */
78018                 destroy : function(silent) {
78019                     /*
78020                      * Silent is to be used in a number of cases
78021                      * 1) When setRoot is called.
78022                      * 2) When destroy on the tree is called
78023                      * 3) For destroying child nodes on a node
78024                      */
78025                     var me = this,
78026                         options = me.destroyOptions;
78027
78028                     if (silent === true) {
78029                         me.clear(true);
78030                         Ext.each(me.childNodes, function(n) {
78031                             n.destroy(true);
78032                         });
78033                         me.childNodes = null;
78034                         delete me.destroyOptions;
78035                         me.callOverridden([options]);
78036                     } else {
78037                         me.destroyOptions = silent;
78038                         // overridden method will be called, since remove will end up calling destroy(true);
78039                         me.remove(true);
78040                     }
78041                 },
78042
78043                 /**
78044                  * Inserts the first node before the second node in this nodes childNodes collection.
78045                  * @param {Ext.data.NodeInterface} node The node to insert
78046                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
78047                  * @return {Ext.data.NodeInterface} The inserted node
78048                  */
78049                 insertBefore : function(node, refNode, suppressEvents) {
78050                     var me = this,
78051                         index     = me.indexOf(refNode),
78052                         oldParent = node.parentNode,
78053                         refIndex  = index,
78054                         ps;
78055
78056                     if (!refNode) { // like standard Dom, refNode can be null for append
78057                         return me.appendChild(node);
78058                     }
78059
78060                     // nothing to do
78061                     if (node == refNode) {
78062                         return false;
78063                     }
78064
78065                     // Make sure it is a record with the NodeInterface
78066                     node = me.createNode(node);
78067
78068                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
78069                         return false;
78070                     }
78071
78072                     // when moving internally, indexes will change after remove
78073                     if (oldParent == me && me.indexOf(node) < index) {
78074                         refIndex--;
78075                     }
78076
78077                     // it's a move, make sure we move it cleanly
78078                     if (oldParent) {
78079                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
78080                             return false;
78081                         }
78082                         oldParent.removeChild(node);
78083                     }
78084
78085                     if (refIndex === 0) {
78086                         me.setFirstChild(node);
78087                     }
78088
78089                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
78090                     node.parentNode = me;
78091
78092                     node.nextSibling = refNode;
78093                     refNode.previousSibling = node;
78094
78095                     ps = me.childNodes[refIndex - 1];
78096                     if (ps) {
78097                         node.previousSibling = ps;
78098                         ps.nextSibling = node;
78099                         ps.updateInfo();
78100                     } else {
78101                         node.previousSibling = null;
78102                     }
78103
78104                     node.updateInfo();
78105
78106                     if (!me.isLoaded()) {
78107                         me.set('loaded', true);
78108                     }
78109                     // If this node didnt have any childnodes before, update myself
78110                     else if (me.childNodes.length === 1) {
78111                         me.set('loaded', me.isLoaded());
78112                     }
78113
78114                     if (suppressEvents !== true) {
78115                         me.fireEvent("insert", me, node, refNode);
78116
78117                         if (oldParent) {
78118                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
78119                         }
78120                     }
78121
78122                     return node;
78123                 },
78124
78125                 /**
78126                  * Insert a node into this node
78127                  * @param {Number} index The zero-based index to insert the node at
78128                  * @param {Ext.data.Model} node The node to insert
78129                  * @return {Ext.data.Model} The record you just inserted
78130                  */
78131                 insertChild: function(index, node) {
78132                     var sibling = this.childNodes[index];
78133                     if (sibling) {
78134                         return this.insertBefore(node, sibling);
78135                     }
78136                     else {
78137                         return this.appendChild(node);
78138                     }
78139                 },
78140
78141                 /**
78142                  * Removes this node from its parent
78143                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78144                  * @return {Ext.data.NodeInterface} this
78145                  */
78146                 remove : function(destroy, suppressEvents) {
78147                     var parentNode = this.parentNode;
78148
78149                     if (parentNode) {
78150                         parentNode.removeChild(this, destroy, suppressEvents, true);
78151                     }
78152                     return this;
78153                 },
78154
78155                 /**
78156                  * Removes all child nodes from this node.
78157                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
78158                  * @return {Ext.data.NodeInterface} this
78159                  */
78160                 removeAll : function(destroy, suppressEvents) {
78161                     var cn = this.childNodes,
78162                         n;
78163
78164                     while ((n = cn[0])) {
78165                         this.removeChild(n, destroy, suppressEvents);
78166                     }
78167                     return this;
78168                 },
78169
78170                 /**
78171                  * Returns the child node at the specified index.
78172                  * @param {Number} index
78173                  * @return {Ext.data.NodeInterface}
78174                  */
78175                 getChildAt : function(index) {
78176                     return this.childNodes[index];
78177                 },
78178
78179                 /**
78180                  * Replaces one child node in this node with another.
78181                  * @param {Ext.data.NodeInterface} newChild The replacement node
78182                  * @param {Ext.data.NodeInterface} oldChild The node to replace
78183                  * @return {Ext.data.NodeInterface} The replaced node
78184                  */
78185                 replaceChild : function(newChild, oldChild, suppressEvents) {
78186                     var s = oldChild ? oldChild.nextSibling : null;
78187
78188                     this.removeChild(oldChild, suppressEvents);
78189                     this.insertBefore(newChild, s, suppressEvents);
78190                     return oldChild;
78191                 },
78192
78193                 /**
78194                  * Returns the index of a child node
78195                  * @param {Ext.data.NodeInterface} node
78196                  * @return {Number} The index of the node or -1 if it was not found
78197                  */
78198                 indexOf : function(child) {
78199                     return Ext.Array.indexOf(this.childNodes, child);
78200                 },
78201
78202                 /**
78203                  * Gets the hierarchical path from the root of the current node.
78204                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
78205                  * @param {String} [separator="/"] A separator to use.
78206                  * @return {String} The node path
78207                  */
78208                 getPath: function(field, separator) {
78209                     field = field || this.idProperty;
78210                     separator = separator || '/';
78211
78212                     var path = [this.get(field)],
78213                         parent = this.parentNode;
78214
78215                     while (parent) {
78216                         path.unshift(parent.get(field));
78217                         parent = parent.parentNode;
78218                     }
78219                     return separator + path.join(separator);
78220                 },
78221
78222                 /**
78223                  * Returns depth of this node (the root node has a depth of 0)
78224                  * @return {Number}
78225                  */
78226                 getDepth : function() {
78227                     return this.get('depth');
78228                 },
78229
78230                 /**
78231                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
78232                  * will be the args provided or the current node. If the function returns false at any point,
78233                  * the bubble is stopped.
78234                  * @param {Function} fn The function to call
78235                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78236                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78237                  */
78238                 bubble : function(fn, scope, args) {
78239                     var p = this;
78240                     while (p) {
78241                         if (fn.apply(scope || p, args || [p]) === false) {
78242                             break;
78243                         }
78244                         p = p.parentNode;
78245                     }
78246                 },
78247
78248                 cascade: function() {
78249                     if (Ext.isDefined(Ext.global.console)) {
78250                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
78251                     }
78252                     return this.cascadeBy.apply(this, arguments);
78253                 },
78254
78255                 /**
78256                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
78257                  * will be the args provided or the current node. If the function returns false at any point,
78258                  * the cascade is stopped on that branch.
78259                  * @param {Function} fn The function to call
78260                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78261                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78262                  */
78263                 cascadeBy : function(fn, scope, args) {
78264                     if (fn.apply(scope || this, args || [this]) !== false) {
78265                         var childNodes = this.childNodes,
78266                             length     = childNodes.length,
78267                             i;
78268
78269                         for (i = 0; i < length; i++) {
78270                             childNodes[i].cascadeBy(fn, scope, args);
78271                         }
78272                     }
78273                 },
78274
78275                 /**
78276                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
78277                  * will be the args provided or the current node. If the function returns false at any point,
78278                  * the iteration stops.
78279                  * @param {Function} fn The function to call
78280                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
78281                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78282                  */
78283                 eachChild : function(fn, scope, args) {
78284                     var childNodes = this.childNodes,
78285                         length     = childNodes.length,
78286                         i;
78287
78288                     for (i = 0; i < length; i++) {
78289                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
78290                             break;
78291                         }
78292                     }
78293                 },
78294
78295                 /**
78296                  * Finds the first child that has the attribute with the specified value.
78297                  * @param {String} attribute The attribute name
78298                  * @param {Object} value The value to search for
78299                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78300                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78301                  */
78302                 findChild : function(attribute, value, deep) {
78303                     return this.findChildBy(function() {
78304                         return this.get(attribute) == value;
78305                     }, null, deep);
78306                 },
78307
78308                 /**
78309                  * Finds the first child by a custom function. The child matches if the function passed returns true.
78310                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
78311                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
78312                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78313                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78314                  */
78315                 findChildBy : function(fn, scope, deep) {
78316                     var cs = this.childNodes,
78317                         len = cs.length,
78318                         i = 0, n, res;
78319
78320                     for (; i < len; i++) {
78321                         n = cs[i];
78322                         if (fn.call(scope || n, n) === true) {
78323                             return n;
78324                         }
78325                         else if (deep) {
78326                             res = n.findChildBy(fn, scope, deep);
78327                             if (res !== null) {
78328                                 return res;
78329                             }
78330                         }
78331                     }
78332
78333                     return null;
78334                 },
78335
78336                 /**
78337                  * Returns true if this node is an ancestor (at any point) of the passed node.
78338                  * @param {Ext.data.NodeInterface} node
78339                  * @return {Boolean}
78340                  */
78341                 contains : function(node) {
78342                     return node.isAncestor(this);
78343                 },
78344
78345                 /**
78346                  * Returns true if the passed node is an ancestor (at any point) of this node.
78347                  * @param {Ext.data.NodeInterface} node
78348                  * @return {Boolean}
78349                  */
78350                 isAncestor : function(node) {
78351                     var p = this.parentNode;
78352                     while (p) {
78353                         if (p == node) {
78354                             return true;
78355                         }
78356                         p = p.parentNode;
78357                     }
78358                     return false;
78359                 },
78360
78361                 /**
78362                  * Sorts this nodes children using the supplied sort function.
78363                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
78364                  * @param {Boolean} [recursive=false] True to apply this sort recursively
78365                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
78366                  */
78367                 sort : function(sortFn, recursive, suppressEvent) {
78368                     var cs  = this.childNodes,
78369                         ln = cs.length,
78370                         i, n;
78371
78372                     if (ln > 0) {
78373                         Ext.Array.sort(cs, sortFn);
78374                         for (i = 0; i < ln; i++) {
78375                             n = cs[i];
78376                             n.previousSibling = cs[i-1];
78377                             n.nextSibling = cs[i+1];
78378
78379                             if (i === 0) {
78380                                 this.setFirstChild(n);
78381                                 n.updateInfo();
78382                             }
78383                             if (i == ln - 1) {
78384                                 this.setLastChild(n);
78385                                 n.updateInfo();
78386                             }
78387                             if (recursive && !n.isLeaf()) {
78388                                 n.sort(sortFn, true, true);
78389                             }
78390                         }
78391
78392                         if (suppressEvent !== true) {
78393                             this.fireEvent('sort', this, cs);
78394                         }
78395                     }
78396                 },
78397
78398                 /**
78399                  * Returns true if this node is expaned
78400                  * @return {Boolean}
78401                  */
78402                 isExpanded: function() {
78403                     return this.get('expanded');
78404                 },
78405
78406                 /**
78407                  * Returns true if this node is loaded
78408                  * @return {Boolean}
78409                  */
78410                 isLoaded: function() {
78411                     return this.get('loaded');
78412                 },
78413
78414                 /**
78415                  * Returns true if this node is loading
78416                  * @return {Boolean}
78417                  */
78418                 isLoading: function() {
78419                     return this.get('loading');
78420                 },
78421
78422                 /**
78423                  * Returns true if this node is the root node
78424                  * @return {Boolean}
78425                  */
78426                 isRoot: function() {
78427                     return !this.parentNode;
78428                 },
78429
78430                 /**
78431                  * Returns true if this node is visible
78432                  * @return {Boolean}
78433                  */
78434                 isVisible: function() {
78435                     var parent = this.parentNode;
78436                     while (parent) {
78437                         if (!parent.isExpanded()) {
78438                             return false;
78439                         }
78440                         parent = parent.parentNode;
78441                     }
78442                     return true;
78443                 },
78444
78445                 /**
78446                  * Expand this node.
78447                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78448                  * @param {Function} [callback] The function to execute once the expand completes
78449                  * @param {Object} [scope] The scope to run the callback in
78450                  */
78451                 expand: function(recursive, callback, scope) {
78452                     var me = this;
78453
78454                     // all paths must call the callback (eventually) or things like
78455                     // selectPath fail
78456
78457                     // First we start by checking if this node is a parent
78458                     if (!me.isLeaf()) {
78459                         // If it's loaded, wait until it loads before proceeding
78460                         if (me.isLoading()) {
78461                             me.on('expand', function(){
78462                                 me.expand(recursive, callback, scope);
78463                             }, me, {single: true});
78464                         } else {
78465                             // Now we check if this record is already expanding or expanded
78466                             if (!me.isExpanded()) {
78467                                 // The TreeStore actually listens for the beforeexpand method and checks
78468                                 // whether we have to asynchronously load the children from the server
78469                                 // first. Thats why we pass a callback function to the event that the
78470                                 // store can call once it has loaded and parsed all the children.
78471                                 me.fireEvent('beforeexpand', me, function(){
78472                                     me.set('expanded', true);
78473                                     me.fireEvent('expand', me, me.childNodes, false);
78474
78475                                     // Call the expandChildren method if recursive was set to true
78476                                     if (recursive) {
78477                                         me.expandChildren(true, callback, scope);
78478                                     } else {
78479                                         Ext.callback(callback, scope || me, [me.childNodes]);
78480                                     }
78481                                 }, me);
78482                             } else if (recursive) {
78483                                 // If it is is already expanded but we want to recursively expand then call expandChildren
78484                                 me.expandChildren(true, callback, scope);
78485                             } else {
78486                                 Ext.callback(callback, scope || me, [me.childNodes]);
78487                             }
78488                         }
78489                     } else {
78490                         // If it's not then we fire the callback right away
78491                         Ext.callback(callback, scope || me); // leaf = no childNodes
78492                     }
78493                 },
78494
78495                 /**
78496                  * Expand all the children of this node.
78497                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78498                  * @param {Function} [callback] The function to execute once all the children are expanded
78499                  * @param {Object} [scope] The scope to run the callback in
78500                  */
78501                 expandChildren: function(recursive, callback, scope) {
78502                     var me = this,
78503                         i = 0,
78504                         nodes = me.childNodes,
78505                         ln = nodes.length,
78506                         node,
78507                         expanding = 0;
78508
78509                     for (; i < ln; ++i) {
78510                         node = nodes[i];
78511                         if (!node.isLeaf() && !node.isExpanded()) {
78512                             expanding++;
78513                             nodes[i].expand(recursive, function () {
78514                                 expanding--;
78515                                 if (callback && !expanding) {
78516                                     Ext.callback(callback, scope || me, [me.childNodes]);
78517                                 }
78518                             });
78519                         }
78520                     }
78521
78522                     if (!expanding && callback) {
78523                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
78524                 },
78525
78526                 /**
78527                  * Collapse this node.
78528                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
78529                  * @param {Function} [callback] The function to execute once the collapse completes
78530                  * @param {Object} [scope] The scope to run the callback in
78531                  */
78532                 collapse: function(recursive, callback, scope) {
78533                     var me = this;
78534
78535                     // First we start by checking if this node is a parent
78536                     if (!me.isLeaf()) {
78537                         // Now we check if this record is already collapsing or collapsed
78538                         if (!me.collapsing && me.isExpanded()) {
78539                             me.fireEvent('beforecollapse', me, function() {
78540                                 me.set('expanded', false);
78541                                 me.fireEvent('collapse', me, me.childNodes, false);
78542
78543                                 // Call the collapseChildren method if recursive was set to true
78544                                 if (recursive) {
78545                                     me.collapseChildren(true, callback, scope);
78546                                 }
78547                                 else {
78548                                     Ext.callback(callback, scope || me, [me.childNodes]);
78549                                 }
78550                             }, me);
78551                         }
78552                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
78553                         else if (recursive) {
78554                             me.collapseChildren(true, callback, scope);
78555                         }
78556                     }
78557                     // If it's not then we fire the callback right away
78558                     else {
78559                         Ext.callback(callback, scope || me, [me.childNodes]);
78560                     }
78561                 },
78562
78563                 /**
78564                  * Collapse all the children of this node.
78565                  * @param {Function} [recursive=false] True to recursively collapse all the children
78566                  * @param {Function} [callback] The function to execute once all the children are collapsed
78567                  * @param {Object} [scope] The scope to run the callback in
78568                  */
78569                 collapseChildren: function(recursive, callback, scope) {
78570                     var me = this,
78571                         i = 0,
78572                         nodes = me.childNodes,
78573                         ln = nodes.length,
78574                         node,
78575                         collapsing = 0;
78576
78577                     for (; i < ln; ++i) {
78578                         node = nodes[i];
78579                         if (!node.isLeaf() && node.isExpanded()) {
78580                             collapsing++;
78581                             nodes[i].collapse(recursive, function () {
78582                                 collapsing--;
78583                                 if (callback && !collapsing) {
78584                                     Ext.callback(callback, scope || me, [me.childNodes]);
78585                                 }
78586                             });
78587                         }
78588                     }
78589
78590                     if (!collapsing && callback) {
78591                         Ext.callback(callback, scope || me, [me.childNodes]);
78592                     }
78593                 }
78594             };
78595         }
78596     }
78597 });
78598 /**
78599  * @class Ext.data.NodeStore
78600  * @extends Ext.data.AbstractStore
78601  * Node Store
78602  * @ignore
78603  */
78604 Ext.define('Ext.data.NodeStore', {
78605     extend: 'Ext.data.Store',
78606     alias: 'store.node',
78607     requires: ['Ext.data.NodeInterface'],
78608     
78609     /**
78610      * @cfg {Ext.data.Model} node
78611      * The Record you want to bind this Store to. Note that
78612      * this record will be decorated with the Ext.data.NodeInterface if this is not the
78613      * case yet.
78614      */
78615     node: null,
78616     
78617     /**
78618      * @cfg {Boolean} recursive
78619      * Set this to true if you want this NodeStore to represent
78620      * all the descendents of the node in its flat data collection. This is useful for
78621      * rendering a tree structure to a DataView and is being used internally by
78622      * the TreeView. Any records that are moved, removed, inserted or appended to the
78623      * node at any depth below the node this store is bound to will be automatically
78624      * updated in this Store's internal flat data structure.
78625      */
78626     recursive: false,
78627     
78628     /** 
78629      * @cfg {Boolean} rootVisible
78630      * False to not include the root node in this Stores collection.
78631      */    
78632     rootVisible: false,
78633     
78634     constructor: function(config) {
78635         var me = this,
78636             node;
78637             
78638         config = config || {};
78639         Ext.apply(me, config);
78640         
78641
78642         config.proxy = {type: 'proxy'};
78643         me.callParent([config]);
78644
78645         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
78646         
78647         node = me.node;
78648         if (node) {
78649             me.node = null;
78650             me.setNode(node);
78651         }
78652     },
78653     
78654     setNode: function(node) {
78655         var me = this;
78656         
78657         if (me.node && me.node != node) {
78658             // We want to unbind our listeners on the old node
78659             me.mun(me.node, {
78660                 expand: me.onNodeExpand,
78661                 collapse: me.onNodeCollapse,
78662                 append: me.onNodeAppend,
78663                 insert: me.onNodeInsert,
78664                 remove: me.onNodeRemove,
78665                 sort: me.onNodeSort,
78666                 scope: me
78667             });
78668             me.node = null;
78669         }
78670         
78671         if (node) {
78672             Ext.data.NodeInterface.decorate(node);
78673             me.removeAll();
78674             if (me.rootVisible) {
78675                 me.add(node);
78676             }
78677             me.mon(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 = node;
78687             if (node.isExpanded() && node.isLoaded()) {
78688                 me.onNodeExpand(node, node.childNodes, true);
78689             }
78690         }
78691     },
78692     
78693     onNodeSort: function(node, childNodes) {
78694         var me = this;
78695         
78696         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
78697             me.onNodeCollapse(node, childNodes, true);
78698             me.onNodeExpand(node, childNodes, true);
78699         }
78700     },
78701     
78702     onNodeExpand: function(parent, records, suppressEvent) {
78703         var me = this,
78704             insertIndex = me.indexOf(parent) + 1,
78705             ln = records ? records.length : 0,
78706             i, record;
78707             
78708         if (!me.recursive && parent !== me.node) {
78709             return;
78710         }
78711         
78712         if (!me.isVisible(parent)) {
78713             return;
78714         }
78715
78716         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
78717             return;
78718         }
78719         
78720         if (ln) {
78721             me.insert(insertIndex, records);
78722             for (i = 0; i < ln; i++) {
78723                 record = records[i];
78724                 if (record.isExpanded()) {
78725                     if (record.isLoaded()) {
78726                         // Take a shortcut                        
78727                         me.onNodeExpand(record, record.childNodes, true);
78728                     }
78729                     else {
78730                         record.set('expanded', false);
78731                         record.expand();
78732                     }
78733                 }
78734             }
78735         }
78736
78737         if (!suppressEvent) {
78738             me.fireEvent('expand', parent, records);
78739         }
78740     },
78741
78742     onNodeCollapse: function(parent, records, suppressEvent) {
78743         var me = this,
78744             ln = records.length,
78745             collapseIndex = me.indexOf(parent) + 1,
78746             i, record;
78747             
78748         if (!me.recursive && parent !== me.node) {
78749             return;
78750         }
78751         
78752         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
78753             return;
78754         }
78755
78756         for (i = 0; i < ln; i++) {
78757             record = records[i];
78758             me.remove(record);
78759             if (record.isExpanded()) {
78760                 me.onNodeCollapse(record, record.childNodes, true);
78761             }
78762         }
78763         
78764         if (!suppressEvent) {
78765             me.fireEvent('collapse', parent, records, collapseIndex);
78766         }
78767     },
78768     
78769     onNodeAppend: function(parent, node, index) {
78770         var me = this,
78771             refNode, sibling;
78772
78773         if (me.isVisible(node)) {
78774             if (index === 0) {
78775                 refNode = parent;
78776             } else {
78777                 sibling = node.previousSibling;
78778                 while (sibling.isExpanded() && sibling.lastChild) {
78779                     sibling = sibling.lastChild;
78780                 }
78781                 refNode = sibling;
78782             }
78783             me.insert(me.indexOf(refNode) + 1, node);
78784             if (!node.isLeaf() && node.isExpanded()) {
78785                 if (node.isLoaded()) {
78786                     // Take a shortcut                        
78787                     me.onNodeExpand(node, node.childNodes, true);
78788                 }
78789                 else {
78790                     node.set('expanded', false);
78791                     node.expand();
78792                 }
78793             }
78794         } 
78795     },
78796     
78797     onNodeInsert: function(parent, node, refNode) {
78798         var me = this,
78799             index = this.indexOf(refNode);
78800             
78801         if (index != -1 && me.isVisible(node)) {
78802             me.insert(index, node);
78803             if (!node.isLeaf() && node.isExpanded()) {
78804                 if (node.isLoaded()) {
78805                     // Take a shortcut                        
78806                     me.onNodeExpand(node, node.childNodes, true);
78807                 }
78808                 else {
78809                     node.set('expanded', false);
78810                     node.expand();
78811                 }
78812             }
78813         }
78814     },
78815     
78816     onNodeRemove: function(parent, node, index) {
78817         var me = this;
78818         if (me.indexOf(node) != -1) {
78819             if (!node.isLeaf() && node.isExpanded()) {
78820                 me.onNodeCollapse(node, node.childNodes, true);
78821             }            
78822             me.remove(node);
78823         }
78824     },
78825     
78826     isVisible: function(node) {
78827         var parent = node.parentNode;
78828         while (parent) {
78829             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
78830                 return true;
78831             }
78832             
78833             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
78834                 return false;
78835             }
78836             
78837             parent = parent.parentNode;
78838         }
78839         return true;
78840     }
78841 });
78842 /**
78843  * @author Ed Spencer
78844  * 
78845  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
78846  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
78847  * it does not contain any actual logic or perform the request itself.
78848  */
78849 Ext.define('Ext.data.Request', {
78850     /**
78851      * @cfg {String} action
78852      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
78853      */
78854     action: undefined,
78855     
78856     /**
78857      * @cfg {Object} params
78858      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
78859      */
78860     params: undefined,
78861     
78862     /**
78863      * @cfg {String} method
78864      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
78865      */
78866     method: 'GET',
78867     
78868     /**
78869      * @cfg {String} url
78870      * The url to access on this Request
78871      */
78872     url: undefined,
78873
78874     /**
78875      * Creates the Request object.
78876      * @param {Object} [config] Config object.
78877      */
78878     constructor: function(config) {
78879         Ext.apply(this, config);
78880     }
78881 });
78882 /**
78883  * @author Don Griffin
78884  *
78885  * This class is a sequential id generator. A simple use of this class would be like so:
78886  *
78887  *     Ext.define('MyApp.data.MyModel', {
78888  *         extend: 'Ext.data.Model',
78889  *         idgen: 'sequential'
78890  *     });
78891  *     // assign id's of 1, 2, 3, etc.
78892  *
78893  * An example of a configured generator would be:
78894  *
78895  *     Ext.define('MyApp.data.MyModel', {
78896  *         extend: 'Ext.data.Model',
78897  *         idgen: {
78898  *             type: 'sequential',
78899  *             prefix: 'ID_',
78900  *             seed: 1000
78901  *         }
78902  *     });
78903  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
78904  *
78905  */
78906 Ext.define('Ext.data.SequentialIdGenerator', {
78907     extend: 'Ext.data.IdGenerator',
78908     alias: 'idgen.sequential',
78909
78910     constructor: function() {
78911         var me = this;
78912
78913         me.callParent(arguments);
78914
78915         me.parts = [ me.prefix, ''];
78916     },
78917
78918     /**
78919      * @cfg {String} prefix
78920      * The string to place in front of the sequential number for each generated id. The
78921      * default is blank.
78922      */
78923     prefix: '',
78924
78925     /**
78926      * @cfg {Number} seed
78927      * The number at which to start generating sequential id's. The default is 1.
78928      */
78929     seed: 1,
78930
78931     /**
78932      * Generates and returns the next id.
78933      * @return {String} The next id.
78934      */
78935     generate: function () {
78936         var me = this,
78937             parts = me.parts;
78938
78939         parts[1] = me.seed++;
78940         return parts.join('');
78941     }
78942 });
78943
78944 /**
78945  * @class Ext.data.Tree
78946  *
78947  * This class is used as a container for a series of nodes. The nodes themselves maintain
78948  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
78949  * to retrieve a node by its identifier: {@link #getNodeById}.
78950  *
78951  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
78952  * centralized fashion. In general this class is not used directly, rather used internally
78953  * by other parts of the framework.
78954  *
78955  */
78956 Ext.define('Ext.data.Tree', {
78957     alias: 'data.tree',
78958
78959     mixins: {
78960         observable: "Ext.util.Observable"
78961     },
78962
78963     /**
78964      * @property {Ext.data.NodeInterface}
78965      * The root node for this tree
78966      */
78967     root: null,
78968
78969     /**
78970      * Creates new Tree object.
78971      * @param {Ext.data.NodeInterface} root (optional) The root node
78972      */
78973     constructor: function(root) {
78974         var me = this;
78975
78976         
78977
78978         me.mixins.observable.constructor.call(me);
78979
78980         if (root) {
78981             me.setRootNode(root);
78982         }
78983     },
78984
78985     /**
78986      * Returns the root node for this tree.
78987      * @return {Ext.data.NodeInterface}
78988      */
78989     getRootNode : function() {
78990         return this.root;
78991     },
78992
78993     /**
78994      * Sets the root node for this tree.
78995      * @param {Ext.data.NodeInterface} node
78996      * @return {Ext.data.NodeInterface} The root node
78997      */
78998     setRootNode : function(node) {
78999         var me = this;
79000
79001         me.root = node;
79002         Ext.data.NodeInterface.decorate(node);
79003
79004         if (me.fireEvent('beforeappend', null, node) !== false) {
79005             node.set('root', true);
79006             node.updateInfo();
79007
79008             me.relayEvents(node, [
79009                 /**
79010                  * @event append
79011                  * @alias Ext.data.NodeInterface#append
79012                  */
79013                 "append",
79014
79015                 /**
79016                  * @event remove
79017                  * @alias Ext.data.NodeInterface#remove
79018                  */
79019                 "remove",
79020
79021                 /**
79022                  * @event move
79023                  * @alias Ext.data.NodeInterface#move
79024                  */
79025                 "move",
79026
79027                 /**
79028                  * @event insert
79029                  * @alias Ext.data.NodeInterface#insert
79030                  */
79031                 "insert",
79032
79033                 /**
79034                  * @event beforeappend
79035                  * @alias Ext.data.NodeInterface#beforeappend
79036                  */
79037                 "beforeappend",
79038
79039                 /**
79040                  * @event beforeremove
79041                  * @alias Ext.data.NodeInterface#beforeremove
79042                  */
79043                 "beforeremove",
79044
79045                 /**
79046                  * @event beforemove
79047                  * @alias Ext.data.NodeInterface#beforemove
79048                  */
79049                 "beforemove",
79050
79051                 /**
79052                  * @event beforeinsert
79053                  * @alias Ext.data.NodeInterface#beforeinsert
79054                  */
79055                 "beforeinsert",
79056
79057                  /**
79058                   * @event expand
79059                   * @alias Ext.data.NodeInterface#expand
79060                   */
79061                  "expand",
79062
79063                  /**
79064                   * @event collapse
79065                   * @alias Ext.data.NodeInterface#collapse
79066                   */
79067                  "collapse",
79068
79069                  /**
79070                   * @event beforeexpand
79071                   * @alias Ext.data.NodeInterface#beforeexpand
79072                   */
79073                  "beforeexpand",
79074
79075                  /**
79076                   * @event beforecollapse
79077                   * @alias Ext.data.NodeInterface#beforecollapse
79078                   */
79079                  "beforecollapse" ,
79080
79081                  /**
79082                   * @event rootchange
79083                   * Fires whenever the root node is changed in the tree.
79084                   * @param {Ext.data.Model} root The new root
79085                   */
79086                  "rootchange"
79087             ]);
79088
79089             node.on({
79090                 scope: me,
79091                 insert: me.onNodeInsert,
79092                 append: me.onNodeAppend,
79093                 remove: me.onNodeRemove
79094             });
79095
79096             me.nodeHash = {};
79097             me.registerNode(node);
79098             me.fireEvent('append', null, node);
79099             me.fireEvent('rootchange', node);
79100         }
79101
79102         return node;
79103     },
79104
79105     /**
79106      * Flattens all the nodes in the tree into an array.
79107      * @private
79108      * @return {Ext.data.NodeInterface[]} The flattened nodes.
79109      */
79110     flatten: function(){
79111         var nodes = [],
79112             hash = this.nodeHash,
79113             key;
79114
79115         for (key in hash) {
79116             if (hash.hasOwnProperty(key)) {
79117                 nodes.push(hash[key]);
79118             }
79119         }
79120         return nodes;
79121     },
79122
79123     /**
79124      * Fired when a node is inserted into the root or one of it's children
79125      * @private
79126      * @param {Ext.data.NodeInterface} parent The parent node
79127      * @param {Ext.data.NodeInterface} node The inserted node
79128      */
79129     onNodeInsert: function(parent, node) {
79130         this.registerNode(node, true);
79131     },
79132
79133     /**
79134      * Fired when a node is appended into the root or one of it's children
79135      * @private
79136      * @param {Ext.data.NodeInterface} parent The parent node
79137      * @param {Ext.data.NodeInterface} node The appended node
79138      */
79139     onNodeAppend: function(parent, node) {
79140         this.registerNode(node, true);
79141     },
79142
79143     /**
79144      * Fired when a node is removed from the root or one of it's children
79145      * @private
79146      * @param {Ext.data.NodeInterface} parent The parent node
79147      * @param {Ext.data.NodeInterface} node The removed node
79148      */
79149     onNodeRemove: function(parent, node) {
79150         this.unregisterNode(node, true);
79151     },
79152
79153     /**
79154      * Gets a node in this tree by its id.
79155      * @param {String} id
79156      * @return {Ext.data.NodeInterface} The match node.
79157      */
79158     getNodeById : function(id) {
79159         return this.nodeHash[id];
79160     },
79161
79162     /**
79163      * Registers a node with the tree
79164      * @private
79165      * @param {Ext.data.NodeInterface} The node to register
79166      * @param {Boolean} [includeChildren] True to unregister any child nodes
79167      */
79168     registerNode : function(node, includeChildren) {
79169         this.nodeHash[node.getId() || node.internalId] = node;
79170         if (includeChildren === true) {
79171             node.eachChild(function(child){
79172                 this.registerNode(child, true);
79173             }, this);
79174         }
79175     },
79176
79177     /**
79178      * Unregisters a node with the tree
79179      * @private
79180      * @param {Ext.data.NodeInterface} The node to unregister
79181      * @param {Boolean} [includeChildren] True to unregister any child nodes
79182      */
79183     unregisterNode : function(node, includeChildren) {
79184         delete this.nodeHash[node.getId() || node.internalId];
79185         if (includeChildren === true) {
79186             node.eachChild(function(child){
79187                 this.unregisterNode(child, true);
79188             }, this);
79189         }
79190     },
79191
79192     /**
79193      * Sorts this tree
79194      * @private
79195      * @param {Function} sorterFn The function to use for sorting
79196      * @param {Boolean} recursive True to perform recursive sorting
79197      */
79198     sort: function(sorterFn, recursive) {
79199         this.getRootNode().sort(sorterFn, recursive);
79200     },
79201
79202      /**
79203      * Filters this tree
79204      * @private
79205      * @param {Function} sorterFn The function to use for filtering
79206      * @param {Boolean} recursive True to perform recursive filtering
79207      */
79208     filter: function(filters, recursive) {
79209         this.getRootNode().filter(filters, recursive);
79210     }
79211 });
79212 /**
79213  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
79214  * It provides convenience methods for loading nodes, as well as the ability to use
79215  * the hierarchical tree structure combined with a store. This class is generally used
79216  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
79217  * the Tree for convenience.
79218  *
79219  * # Using Models
79220  *
79221  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
79222  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
79223  * in the {@link Ext.data.NodeInterface} documentation.
79224  *
79225  * # Reading Nested Data
79226  *
79227  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
79228  * so the reader can find nested data for each node. If a root is not specified, it will default to
79229  * 'children'.
79230  */
79231 Ext.define('Ext.data.TreeStore', {
79232     extend: 'Ext.data.AbstractStore',
79233     alias: 'store.tree',
79234     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
79235
79236     /**
79237      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
79238      * The root node for this store. For example:
79239      *
79240      *     root: {
79241      *         expanded: true,
79242      *         text: "My Root",
79243      *         children: [
79244      *             { text: "Child 1", leaf: true },
79245      *             { text: "Child 2", expanded: true, children: [
79246      *                 { text: "GrandChild", leaf: true }
79247      *             ] }
79248      *         ]
79249      *     }
79250      *
79251      * Setting the `root` config option is the same as calling {@link #setRootNode}.
79252      */
79253
79254     /**
79255      * @cfg {Boolean} clearOnLoad
79256      * Remove previously existing child nodes before loading. Default to true.
79257      */
79258     clearOnLoad : true,
79259
79260     /**
79261      * @cfg {String} nodeParam
79262      * The name of the parameter sent to the server which contains the identifier of the node.
79263      * Defaults to 'node'.
79264      */
79265     nodeParam: 'node',
79266
79267     /**
79268      * @cfg {String} defaultRootId
79269      * The default root id. Defaults to 'root'
79270      */
79271     defaultRootId: 'root',
79272
79273     /**
79274      * @cfg {String} defaultRootProperty
79275      * The root property to specify on the reader if one is not explicitly defined.
79276      */
79277     defaultRootProperty: 'children',
79278
79279     /**
79280      * @cfg {Boolean} folderSort
79281      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
79282      */
79283     folderSort: false,
79284
79285     constructor: function(config) {
79286         var me = this,
79287             root,
79288             fields;
79289
79290         config = Ext.apply({}, config);
79291
79292         /**
79293          * If we have no fields declare for the store, add some defaults.
79294          * These will be ignored if a model is explicitly specified.
79295          */
79296         fields = config.fields || me.fields;
79297         if (!fields) {
79298             config.fields = [{name: 'text', type: 'string'}];
79299         }
79300
79301         me.callParent([config]);
79302
79303         // We create our data tree.
79304         me.tree = Ext.create('Ext.data.Tree');
79305
79306         me.relayEvents(me.tree, [
79307             /**
79308              * @event append
79309              * @alias Ext.data.Tree#append
79310              */
79311             "append",
79312
79313             /**
79314              * @event remove
79315              * @alias Ext.data.Tree#remove
79316              */
79317             "remove",
79318
79319             /**
79320              * @event move
79321              * @alias Ext.data.Tree#move
79322              */
79323             "move",
79324
79325             /**
79326              * @event insert
79327              * @alias Ext.data.Tree#insert
79328              */
79329             "insert",
79330
79331             /**
79332              * @event beforeappend
79333              * @alias Ext.data.Tree#beforeappend
79334              */
79335             "beforeappend",
79336
79337             /**
79338              * @event beforeremove
79339              * @alias Ext.data.Tree#beforeremove
79340              */
79341             "beforeremove",
79342
79343             /**
79344              * @event beforemove
79345              * @alias Ext.data.Tree#beforemove
79346              */
79347             "beforemove",
79348
79349             /**
79350              * @event beforeinsert
79351              * @alias Ext.data.Tree#beforeinsert
79352              */
79353             "beforeinsert",
79354
79355              /**
79356               * @event expand
79357               * @alias Ext.data.Tree#expand
79358               */
79359              "expand",
79360
79361              /**
79362               * @event collapse
79363               * @alias Ext.data.Tree#collapse
79364               */
79365              "collapse",
79366
79367              /**
79368               * @event beforeexpand
79369               * @alias Ext.data.Tree#beforeexpand
79370               */
79371              "beforeexpand",
79372
79373              /**
79374               * @event beforecollapse
79375               * @alias Ext.data.Tree#beforecollapse
79376               */
79377              "beforecollapse",
79378
79379              /**
79380               * @event rootchange
79381               * @alias Ext.data.Tree#rootchange
79382               */
79383              "rootchange"
79384         ]);
79385
79386         me.tree.on({
79387             scope: me,
79388             remove: me.onNodeRemove,
79389             // this event must follow the relay to beforeitemexpand to allow users to
79390             // cancel the expand:
79391             beforeexpand: me.onBeforeNodeExpand,
79392             beforecollapse: me.onBeforeNodeCollapse,
79393             append: me.onNodeAdded,
79394             insert: me.onNodeAdded
79395         });
79396
79397         me.onBeforeSort();
79398
79399         root = me.root;
79400         if (root) {
79401             delete me.root;
79402             me.setRootNode(root);
79403         }
79404
79405         me.addEvents(
79406             /**
79407              * @event sort
79408              * Fires when this TreeStore is sorted.
79409              * @param {Ext.data.NodeInterface} node The node that is sorted.
79410              */
79411             'sort'
79412         );
79413
79414         if (Ext.isDefined(me.nodeParameter)) {
79415             if (Ext.isDefined(Ext.global.console)) {
79416                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
79417             }
79418             me.nodeParam = me.nodeParameter;
79419             delete me.nodeParameter;
79420         }
79421     },
79422
79423     // inherit docs
79424     setProxy: function(proxy) {
79425         var reader,
79426             needsRoot;
79427
79428         if (proxy instanceof Ext.data.proxy.Proxy) {
79429             // proxy instance, check if a root was set
79430             needsRoot = Ext.isEmpty(proxy.getReader().root);
79431         } else if (Ext.isString(proxy)) {
79432             // string type, means a reader can't be set
79433             needsRoot = true;
79434         } else {
79435             // object, check if a reader and a root were specified.
79436             reader = proxy.reader;
79437             needsRoot = !(reader && !Ext.isEmpty(reader.root));
79438         }
79439         proxy = this.callParent(arguments);
79440         if (needsRoot) {
79441             reader = proxy.getReader();
79442             reader.root = this.defaultRootProperty;
79443             // force rebuild
79444             reader.buildExtractors(true);
79445         }
79446     },
79447
79448     // inherit docs
79449     onBeforeSort: function() {
79450         if (this.folderSort) {
79451             this.sort({
79452                 property: 'leaf',
79453                 direction: 'ASC'
79454             }, 'prepend', false);
79455         }
79456     },
79457
79458     /**
79459      * Called before a node is expanded.
79460      * @private
79461      * @param {Ext.data.NodeInterface} node The node being expanded.
79462      * @param {Function} callback The function to run after the expand finishes
79463      * @param {Object} scope The scope in which to run the callback function
79464      */
79465     onBeforeNodeExpand: function(node, callback, scope) {
79466         if (node.isLoaded()) {
79467             Ext.callback(callback, scope || node, [node.childNodes]);
79468         }
79469         else if (node.isLoading()) {
79470             this.on('load', function() {
79471                 Ext.callback(callback, scope || node, [node.childNodes]);
79472             }, this, {single: true});
79473         }
79474         else {
79475             this.read({
79476                 node: node,
79477                 callback: function() {
79478                     Ext.callback(callback, scope || node, [node.childNodes]);
79479                 }
79480             });
79481         }
79482     },
79483
79484     //inherit docs
79485     getNewRecords: function() {
79486         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
79487     },
79488
79489     //inherit docs
79490     getUpdatedRecords: function() {
79491         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
79492     },
79493
79494     /**
79495      * Called before a node is collapsed.
79496      * @private
79497      * @param {Ext.data.NodeInterface} node The node being collapsed.
79498      * @param {Function} callback The function to run after the collapse finishes
79499      * @param {Object} scope The scope in which to run the callback function
79500      */
79501     onBeforeNodeCollapse: function(node, callback, scope) {
79502         callback.call(scope || node, node.childNodes);
79503     },
79504
79505     onNodeRemove: function(parent, node) {
79506         var removed = this.removed;
79507
79508         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
79509             removed.push(node);
79510         }
79511     },
79512
79513     onNodeAdded: function(parent, node) {
79514         var proxy = this.getProxy(),
79515             reader = proxy.getReader(),
79516             data = node.raw || node.data,
79517             dataRoot, children;
79518
79519         Ext.Array.remove(this.removed, node);
79520
79521         if (!node.isLeaf() && !node.isLoaded()) {
79522             dataRoot = reader.getRoot(data);
79523             if (dataRoot) {
79524                 this.fillNode(node, reader.extractData(dataRoot));
79525                 delete data[reader.root];
79526             }
79527         }
79528     },
79529
79530     /**
79531      * Sets the root node for this store.  See also the {@link #root} config option.
79532      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
79533      * @return {Ext.data.NodeInterface} The new root
79534      */
79535     setRootNode: function(root) {
79536         var me = this;
79537
79538         root = root || {};
79539         if (!root.isNode) {
79540             // create a default rootNode and create internal data struct.
79541             Ext.applyIf(root, {
79542                 id: me.defaultRootId,
79543                 text: 'Root',
79544                 allowDrag: false
79545             });
79546             root = Ext.ModelManager.create(root, me.model);
79547         }
79548         Ext.data.NodeInterface.decorate(root);
79549
79550         // Because we have decorated the model with new fields,
79551         // we need to build new extactor functions on the reader.
79552         me.getProxy().getReader().buildExtractors(true);
79553
79554         // When we add the root to the tree, it will automaticaly get the NodeInterface
79555         me.tree.setRootNode(root);
79556
79557         // If the user has set expanded: true on the root, we want to call the expand function
79558         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
79559             me.load({
79560                 node: root
79561             });
79562         }
79563
79564         return root;
79565     },
79566
79567     /**
79568      * Returns the root node for this tree.
79569      * @return {Ext.data.NodeInterface}
79570      */
79571     getRootNode: function() {
79572         return this.tree.getRootNode();
79573     },
79574
79575     /**
79576      * Returns the record node by id
79577      * @return {Ext.data.NodeInterface}
79578      */
79579     getNodeById: function(id) {
79580         return this.tree.getNodeById(id);
79581     },
79582
79583     /**
79584      * Loads the Store using its configured {@link #proxy}.
79585      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
79586      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
79587      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
79588      * default to the root node.
79589      */
79590     load: function(options) {
79591         options = options || {};
79592         options.params = options.params || {};
79593
79594         var me = this,
79595             node = options.node || me.tree.getRootNode(),
79596             root;
79597
79598         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
79599         // create one for them.
79600         if (!node) {
79601             node = me.setRootNode({
79602                 expanded: true
79603             });
79604         }
79605
79606         if (me.clearOnLoad) {
79607             node.removeAll(true);
79608         }
79609
79610         Ext.applyIf(options, {
79611             node: node
79612         });
79613         options.params[me.nodeParam] = node ? node.getId() : 'root';
79614
79615         if (node) {
79616             node.set('loading', true);
79617         }
79618
79619         return me.callParent([options]);
79620     },
79621
79622
79623     /**
79624      * Fills a node with a series of child records.
79625      * @private
79626      * @param {Ext.data.NodeInterface} node The node to fill
79627      * @param {Ext.data.Model[]} records The records to add
79628      */
79629     fillNode: function(node, records) {
79630         var me = this,
79631             ln = records ? records.length : 0,
79632             i = 0, sortCollection;
79633
79634         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
79635             sortCollection = Ext.create('Ext.util.MixedCollection');
79636             sortCollection.addAll(records);
79637             sortCollection.sort(me.sorters.items);
79638             records = sortCollection.items;
79639         }
79640
79641         node.set('loaded', true);
79642         for (; i < ln; i++) {
79643             node.appendChild(records[i], undefined, true);
79644         }
79645
79646         return records;
79647     },
79648
79649     // inherit docs
79650     onProxyLoad: function(operation) {
79651         var me = this,
79652             successful = operation.wasSuccessful(),
79653             records = operation.getRecords(),
79654             node = operation.node;
79655
79656         me.loading = false;
79657         node.set('loading', false);
79658         if (successful) {
79659             records = me.fillNode(node, records);
79660         }
79661         // The load event has an extra node parameter
79662         // (differing from the load event described in AbstractStore)
79663         /**
79664          * @event load
79665          * Fires whenever the store reads data from a remote data source.
79666          * @param {Ext.data.TreeStore} this
79667          * @param {Ext.data.NodeInterface} node The node that was loaded.
79668          * @param {Ext.data.Model[]} records An array of records.
79669          * @param {Boolean} successful True if the operation was successful.
79670          */
79671         // deprecate read?
79672         me.fireEvent('read', me, operation.node, records, successful);
79673         me.fireEvent('load', me, operation.node, records, successful);
79674         //this is a callback that would have been passed to the 'read' function and is optional
79675         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
79676     },
79677
79678     /**
79679      * Creates any new records when a write is returned from the server.
79680      * @private
79681      * @param {Ext.data.Model[]} records The array of new records
79682      * @param {Ext.data.Operation} operation The operation that just completed
79683      * @param {Boolean} success True if the operation was successful
79684      */
79685     onCreateRecords: function(records, operation, success) {
79686         if (success) {
79687             var i = 0,
79688                 length = records.length,
79689                 originalRecords = operation.records,
79690                 parentNode,
79691                 record,
79692                 original,
79693                 index;
79694
79695             /*
79696              * Loop over each record returned from the server. Assume they are
79697              * returned in order of how they were sent. If we find a matching
79698              * record, replace it with the newly created one.
79699              */
79700             for (; i < length; ++i) {
79701                 record = records[i];
79702                 original = originalRecords[i];
79703                 if (original) {
79704                     parentNode = original.parentNode;
79705                     if (parentNode) {
79706                         // prevent being added to the removed cache
79707                         original.isReplace = true;
79708                         parentNode.replaceChild(record, original);
79709                         delete original.isReplace;
79710                     }
79711                     record.phantom = false;
79712                 }
79713             }
79714         }
79715     },
79716
79717     /**
79718      * Updates any records when a write is returned from the server.
79719      * @private
79720      * @param {Ext.data.Model[]} records The array of updated records
79721      * @param {Ext.data.Operation} operation The operation that just completed
79722      * @param {Boolean} success True if the operation was successful
79723      */
79724     onUpdateRecords: function(records, operation, success){
79725         if (success) {
79726             var me = this,
79727                 i = 0,
79728                 length = records.length,
79729                 data = me.data,
79730                 original,
79731                 parentNode,
79732                 record;
79733
79734             for (; i < length; ++i) {
79735                 record = records[i];
79736                 original = me.tree.getNodeById(record.getId());
79737                 parentNode = original.parentNode;
79738                 if (parentNode) {
79739                     // prevent being added to the removed cache
79740                     original.isReplace = true;
79741                     parentNode.replaceChild(record, original);
79742                     original.isReplace = false;
79743                 }
79744             }
79745         }
79746     },
79747
79748     /**
79749      * Removes any records when a write is returned from the server.
79750      * @private
79751      * @param {Ext.data.Model[]} records The array of removed records
79752      * @param {Ext.data.Operation} operation The operation that just completed
79753      * @param {Boolean} success True if the operation was successful
79754      */
79755     onDestroyRecords: function(records, operation, success){
79756         if (success) {
79757             this.removed = [];
79758         }
79759     },
79760
79761     // inherit docs
79762     removeAll: function() {
79763         this.getRootNode().destroy(true);
79764         this.fireEvent('clear', this);
79765     },
79766
79767     // inherit docs
79768     doSort: function(sorterFn) {
79769         var me = this;
79770         if (me.remoteSort) {
79771             //the load function will pick up the new sorters and request the sorted data from the proxy
79772             me.load();
79773         } else {
79774             me.tree.sort(sorterFn, true);
79775             me.fireEvent('datachanged', me);
79776         }
79777         me.fireEvent('sort', me);
79778     }
79779 });
79780
79781 /**
79782  * @extend Ext.data.IdGenerator
79783  * @author Don Griffin
79784  *
79785  * This class generates UUID's according to RFC 4122. This class has a default id property.
79786  * This means that a single instance is shared unless the id property is overridden. Thus,
79787  * two {@link Ext.data.Model} instances configured like the following share one generator:
79788  *
79789  *     Ext.define('MyApp.data.MyModelX', {
79790  *         extend: 'Ext.data.Model',
79791  *         idgen: 'uuid'
79792  *     });
79793  *
79794  *     Ext.define('MyApp.data.MyModelY', {
79795  *         extend: 'Ext.data.Model',
79796  *         idgen: 'uuid'
79797  *     });
79798  *
79799  * This allows all models using this class to share a commonly configured instance.
79800  *
79801  * # Using Version 1 ("Sequential") UUID's
79802  *
79803  * If a server can provide a proper timestamp and a "cryptographic quality random number"
79804  * (as described in RFC 4122), the shared instance can be configured as follows:
79805  *
79806  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79807  *         version: 1,
79808  *         clockSeq: clock, // 14 random bits
79809  *         salt: salt,      // 48 secure random bits (the Node field)
79810  *         timestamp: ts    // timestamp per Section 4.1.4
79811  *     });
79812  *
79813  *     // or these values can be split into 32-bit chunks:
79814  *
79815  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79816  *         version: 1,
79817  *         clockSeq: clock,
79818  *         salt: { lo: saltLow32, hi: saltHigh32 },
79819  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
79820  *     });
79821  *
79822  * This approach improves the generator's uniqueness by providing a valid timestamp and
79823  * higher quality random data. Version 1 UUID's should not be used unless this information
79824  * can be provided by a server and care should be taken to avoid caching of this data.
79825  *
79826  * See http://www.ietf.org/rfc/rfc4122.txt for details.
79827  */
79828 Ext.define('Ext.data.UuidGenerator', function () {
79829     var twoPow14 = Math.pow(2, 14),
79830         twoPow16 = Math.pow(2, 16),
79831         twoPow28 = Math.pow(2, 28),
79832         twoPow32 = Math.pow(2, 32);
79833
79834     function toHex (value, length) {
79835         var ret = value.toString(16);
79836         if (ret.length > length) {
79837             ret = ret.substring(ret.length - length); // right-most digits
79838         } else if (ret.length < length) {
79839             ret = Ext.String.leftPad(ret, length, '0');
79840         }
79841         return ret;
79842     }
79843
79844     function rand (lo, hi) {
79845         var v = Math.random() * (hi - lo + 1);
79846         return Math.floor(v) + lo;
79847     }
79848
79849     function split (bignum) {
79850         if (typeof(bignum) == 'number') {
79851             var hi = Math.floor(bignum / twoPow32);
79852             return {
79853                 lo: Math.floor(bignum - hi * twoPow32),
79854                 hi: hi
79855             };
79856         }
79857         return bignum;
79858     }
79859
79860     return {
79861         extend: 'Ext.data.IdGenerator',
79862
79863         alias: 'idgen.uuid',
79864
79865         id: 'uuid', // shared by default
79866
79867         /**
79868          * @property {Number/Object} salt
79869          * When created, this value is a 48-bit number. For computation, this value is split
79870          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79871          */
79872
79873         /**
79874          * @property {Number/Object} timestamp
79875          * When created, this value is a 60-bit number. For computation, this value is split
79876          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79877          */
79878
79879         /**
79880          * @cfg {Number} version
79881          * The Version of UUID. Supported values are:
79882          *
79883          *  * 1 : Time-based, "sequential" UUID.
79884          *  * 4 : Pseudo-random UUID.
79885          *
79886          * The default is 4.
79887          */
79888         version: 4,
79889
79890         constructor: function() {
79891             var me = this;
79892
79893             me.callParent(arguments);
79894
79895             me.parts = [];
79896             me.init();
79897         },
79898
79899         generate: function () {
79900             var me = this,
79901                 parts = me.parts,
79902                 ts = me.timestamp;
79903
79904             /*
79905                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
79906
79907                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79908                |                          time_low                             |
79909                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79910                |           time_mid            |  ver  |        time_hi        |
79911                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79912                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
79913                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79914                |                         salt (2-5)                            |
79915                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79916
79917                          time_mid      clock_hi (low 6 bits)
79918                 time_low     | time_hi |clock_lo
79919                     |        |     |   || salt[0]
79920                     |        |     |   ||   | salt[1..5]
79921                     v        v     v   vv   v v
79922                     0badf00d-aced-1def-b123-dfad0badbeef
79923                                   ^    ^     ^
79924                             version    |     multicast (low bit)
79925                                        |
79926                                     reserved (upper 2 bits)
79927             */
79928             parts[0] = toHex(ts.lo, 8);
79929             parts[1] = toHex(ts.hi & 0xFFFF, 4);
79930             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
79931             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
79932                        toHex(me.clockSeq & 0xFF, 2);
79933             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
79934
79935             if (me.version == 4) {
79936                 me.init(); // just regenerate all the random values...
79937             } else {
79938                 // sequentially increment the timestamp...
79939                 ++ts.lo;
79940                 if (ts.lo >= twoPow32) { // if (overflow)
79941                     ts.lo = 0;
79942                     ++ts.hi;
79943                 }
79944             }
79945
79946             return parts.join('-').toLowerCase();
79947         },
79948
79949         getRecId: function (rec) {
79950             return rec.getId();
79951         },
79952
79953         /**
79954          * @private
79955          */
79956         init: function () {
79957             var me = this,
79958                 salt, time;
79959
79960             if (me.version == 4) {
79961                 // See RFC 4122 (Secion 4.4)
79962                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
79963                 //      or the saved node ID is different than the current node ID,
79964                 //      generate a random clock sequence value.
79965                 me.clockSeq = rand(0, twoPow14-1);
79966
79967                 // we run this on every id generation...
79968                 salt = me.salt || (me.salt = {});
79969                 time = me.timestamp || (me.timestamp = {});
79970
79971                 // See RFC 4122 (Secion 4.4)
79972                 salt.lo = rand(0, twoPow32-1);
79973                 salt.hi = rand(0, twoPow16-1);
79974                 time.lo = rand(0, twoPow32-1);
79975                 time.hi = rand(0, twoPow28-1);
79976             } else {
79977                 // this is run only once per-instance
79978                 me.salt = split(me.salt);
79979                 me.timestamp = split(me.timestamp);
79980
79981                 // Set multicast bit: "the least significant bit of the first octet of the
79982                 // node ID" (nodeId = salt for this implementation):
79983                 me.salt.hi |= 0x100;
79984             }
79985         },
79986
79987         /**
79988          * Reconfigures this generator given new config properties.
79989          */
79990         reconfigure: function (config) {
79991             Ext.apply(this, config);
79992             this.init();
79993         }
79994     };
79995 }());
79996
79997 /**
79998  * @author Ed Spencer
79999  * @class Ext.data.XmlStore
80000  * @extends Ext.data.Store
80001  * @private
80002  * @ignore
80003  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
80004  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
80005  * <p>A store configuration would be something like:<pre><code>
80006 var store = new Ext.data.XmlStore({
80007     // store configs
80008     autoDestroy: true,
80009     storeId: 'myStore',
80010     url: 'sheldon.xml', // automatically configures a HttpProxy
80011     // reader configs
80012     record: 'Item', // records will have an "Item" tag
80013     idPath: 'ASIN',
80014     totalRecords: '@TotalResults'
80015     fields: [
80016         // set up the fields mapping into the xml doc
80017         // The first needs mapping, the others are very basic
80018         {name: 'Author', mapping: 'ItemAttributes > Author'},
80019         'Title', 'Manufacturer', 'ProductGroup'
80020     ]
80021 });
80022  * </code></pre></p>
80023  * <p>This store is configured to consume a returned object of the form:<pre><code>
80024 &#60?xml version="1.0" encoding="UTF-8"?>
80025 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
80026     &#60Items>
80027         &#60Request>
80028             &#60IsValid>True&#60/IsValid>
80029             &#60ItemSearchRequest>
80030                 &#60Author>Sidney Sheldon&#60/Author>
80031                 &#60SearchIndex>Books&#60/SearchIndex>
80032             &#60/ItemSearchRequest>
80033         &#60/Request>
80034         &#60TotalResults>203&#60/TotalResults>
80035         &#60TotalPages>21&#60/TotalPages>
80036         &#60Item>
80037             &#60ASIN>0446355453&#60/ASIN>
80038             &#60DetailPageURL>
80039                 http://www.amazon.com/
80040             &#60/DetailPageURL>
80041             &#60ItemAttributes>
80042                 &#60Author>Sidney Sheldon&#60/Author>
80043                 &#60Manufacturer>Warner Books&#60/Manufacturer>
80044                 &#60ProductGroup>Book&#60/ProductGroup>
80045                 &#60Title>Master of the Game&#60/Title>
80046             &#60/ItemAttributes>
80047         &#60/Item>
80048     &#60/Items>
80049 &#60/ItemSearchResponse>
80050  * </code></pre>
80051  * An object literal of this form could also be used as the {@link #data} config option.</p>
80052  * <p><b>Note:</b> This class accepts all of the configuration options of
80053  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
80054  * @xtype xmlstore
80055  */
80056 Ext.define('Ext.data.XmlStore', {
80057     extend: 'Ext.data.Store',
80058     alternateClassName: 'Ext.data.XmlStore',
80059     alias: 'store.xml',
80060
80061     /**
80062      * @cfg {Ext.data.DataReader} reader @hide
80063      */
80064     constructor: function(config){
80065         config = config || {};
80066         config = config || {};
80067
80068         Ext.applyIf(config, {
80069             proxy: {
80070                 type: 'ajax',
80071                 reader: 'xml',
80072                 writer: 'xml'
80073             }
80074         });
80075
80076         this.callParent([config]);
80077     }
80078 });
80079
80080 /**
80081  * @author Ed Spencer
80082  *
80083  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
80084  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
80085  * @private
80086  */
80087 Ext.define('Ext.data.proxy.Client', {
80088     extend: 'Ext.data.proxy.Proxy',
80089     alternateClassName: 'Ext.data.ClientProxy',
80090
80091     /**
80092      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
80093      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
80094      */
80095     clear: function() {
80096     }
80097 });
80098 /**
80099  * @author Ed Spencer
80100  *
80101  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
80102  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
80103  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
80104  *
80105  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
80106  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
80107  * injected might look like this:
80108  *
80109  *     <script src="http://domainB.com/users?callback=someCallback"></script>
80110  *
80111  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
80112  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
80113  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
80114  * long as the server formats the response to look like this, everything will work:
80115  *
80116  *     someCallback({
80117  *         users: [
80118  *             {
80119  *                 id: 1,
80120  *                 name: "Ed Spencer",
80121  *                 email: "ed@sencha.com"
80122  *             }
80123  *         ]
80124  *     });
80125  *
80126  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
80127  * object that the server returned.
80128  *
80129  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
80130  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
80131  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
80132  * we might set that up:
80133  *
80134  *     Ext.define('User', {
80135  *         extend: 'Ext.data.Model',
80136  *         fields: ['id', 'name', 'email']
80137  *     });
80138  *
80139  *     var store = Ext.create('Ext.data.Store', {
80140  *         model: 'User',
80141  *         proxy: {
80142  *             type: 'jsonp',
80143  *             url : 'http://domainB.com/users'
80144  *         }
80145  *     });
80146  *
80147  *     store.load();
80148  *
80149  * 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
80150  * like this:
80151  *
80152  *     <script src="http://domainB.com/users?callback=callback1"></script>
80153  *
80154  * # Customization
80155  *
80156  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
80157  *
80158  *     var store = Ext.create('Ext.data.Store', {
80159  *         model: 'User',
80160  *         proxy: {
80161  *             type: 'jsonp',
80162  *             url : 'http://domainB.com/users',
80163  *             callbackKey: 'theCallbackFunction'
80164  *         }
80165  *     });
80166  *
80167  *     store.load();
80168  *
80169  * Would inject a script tag like this:
80170  *
80171  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
80172  *
80173  * # Implementing on the server side
80174  *
80175  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
80176  * achieve this using Java, PHP and ASP.net:
80177  *
80178  * Java:
80179  *
80180  *     boolean jsonP = false;
80181  *     String cb = request.getParameter("callback");
80182  *     if (cb != null) {
80183  *         jsonP = true;
80184  *         response.setContentType("text/javascript");
80185  *     } else {
80186  *         response.setContentType("application/x-json");
80187  *     }
80188  *     Writer out = response.getWriter();
80189  *     if (jsonP) {
80190  *         out.write(cb + "(");
80191  *     }
80192  *     out.print(dataBlock.toJsonString());
80193  *     if (jsonP) {
80194  *         out.write(");");
80195  *     }
80196  *
80197  * PHP:
80198  *
80199  *     $callback = $_REQUEST['callback'];
80200  *
80201  *     // Create the output object.
80202  *     $output = array('a' => 'Apple', 'b' => 'Banana');
80203  *
80204  *     //start output
80205  *     if ($callback) {
80206  *         header('Content-Type: text/javascript');
80207  *         echo $callback . '(' . json_encode($output) . ');';
80208  *     } else {
80209  *         header('Content-Type: application/x-json');
80210  *         echo json_encode($output);
80211  *     }
80212  *
80213  * ASP.net:
80214  *
80215  *     String jsonString = "{success: true}";
80216  *     String cb = Request.Params.Get("callback");
80217  *     String responseString = "";
80218  *     if (!String.IsNullOrEmpty(cb)) {
80219  *         responseString = cb + "(" + jsonString + ")";
80220  *     } else {
80221  *         responseString = jsonString;
80222  *     }
80223  *     Response.Write(responseString);
80224  */
80225 Ext.define('Ext.data.proxy.JsonP', {
80226     extend: 'Ext.data.proxy.Server',
80227     alternateClassName: 'Ext.data.ScriptTagProxy',
80228     alias: ['proxy.jsonp', 'proxy.scripttag'],
80229     requires: ['Ext.data.JsonP'],
80230
80231     defaultWriterType: 'base',
80232
80233     /**
80234      * @cfg {String} callbackKey
80235      * See {@link Ext.data.JsonP#callbackKey}.
80236      */
80237     callbackKey : 'callback',
80238
80239     /**
80240      * @cfg {String} recordParam
80241      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
80242      * 'records'
80243      */
80244     recordParam: 'records',
80245
80246     /**
80247      * @cfg {Boolean} autoAppendParams
80248      * True to automatically append the request's params to the generated url. Defaults to true
80249      */
80250     autoAppendParams: true,
80251
80252     constructor: function(){
80253         this.addEvents(
80254             /**
80255              * @event
80256              * Fires when the server returns an exception
80257              * @param {Ext.data.proxy.Proxy} this
80258              * @param {Ext.data.Request} request The request that was sent
80259              * @param {Ext.data.Operation} operation The operation that triggered the request
80260              */
80261             'exception'
80262         );
80263         this.callParent(arguments);
80264     },
80265
80266     /**
80267      * @private
80268      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
80269      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
80270      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
80271      * @param {Function} callback A callback function to execute when the Operation has been completed
80272      * @param {Object} scope The scope to execute the callback in
80273      */
80274     doRequest: function(operation, callback, scope) {
80275         //generate the unique IDs for this request
80276         var me      = this,
80277             writer  = me.getWriter(),
80278             request = me.buildRequest(operation),
80279             params = request.params;
80280
80281         if (operation.allowWrite()) {
80282             request = writer.write(request);
80283         }
80284
80285         // apply JsonP proxy-specific attributes to the Request
80286         Ext.apply(request, {
80287             callbackKey: me.callbackKey,
80288             timeout: me.timeout,
80289             scope: me,
80290             disableCaching: false, // handled by the proxy
80291             callback: me.createRequestCallback(request, operation, callback, scope)
80292         });
80293
80294         // prevent doubling up
80295         if (me.autoAppendParams) {
80296             request.params = {};
80297         }
80298
80299         request.jsonp = Ext.data.JsonP.request(request);
80300         // restore on the request
80301         request.params = params;
80302         operation.setStarted();
80303         me.lastRequest = request;
80304
80305         return request;
80306     },
80307
80308     /**
80309      * @private
80310      * Creates and returns the function that is called when the request has completed. The returned function
80311      * should accept a Response object, which contains the response to be read by the configured Reader.
80312      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
80313      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
80314      * theCallback refers to the callback argument received by this function.
80315      * See {@link #doRequest} for details.
80316      * @param {Ext.data.Request} request The Request object
80317      * @param {Ext.data.Operation} operation The Operation being executed
80318      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
80319      * passed to doRequest
80320      * @param {Object} scope The scope in which to execute the callback function
80321      * @return {Function} The callback function
80322      */
80323     createRequestCallback: function(request, operation, callback, scope) {
80324         var me = this;
80325
80326         return function(success, response, errorType) {
80327             delete me.lastRequest;
80328             me.processResponse(success, operation, request, response, callback, scope);
80329         };
80330     },
80331
80332     // inherit docs
80333     setException: function(operation, response) {
80334         operation.setException(operation.request.jsonp.errorType);
80335     },
80336
80337
80338     /**
80339      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
80340      * @param {Ext.data.Request} request The request object
80341      * @return {String} The url
80342      */
80343     buildUrl: function(request) {
80344         var me      = this,
80345             url     = me.callParent(arguments),
80346             params  = Ext.apply({}, request.params),
80347             filters = params.filters,
80348             records,
80349             filter, i;
80350
80351         delete params.filters;
80352
80353         if (me.autoAppendParams) {
80354             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
80355         }
80356
80357         if (filters && filters.length) {
80358             for (i = 0; i < filters.length; i++) {
80359                 filter = filters[i];
80360
80361                 if (filter.value) {
80362                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
80363                 }
80364             }
80365         }
80366
80367         //if there are any records present, append them to the url also
80368         records = request.records;
80369
80370         if (Ext.isArray(records) && records.length > 0) {
80371             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
80372         }
80373
80374         return url;
80375     },
80376
80377     //inherit docs
80378     destroy: function() {
80379         this.abort();
80380         this.callParent();
80381     },
80382
80383     /**
80384      * Aborts the current server request if one is currently running
80385      */
80386     abort: function() {
80387         var lastRequest = this.lastRequest;
80388         if (lastRequest) {
80389             Ext.data.JsonP.abort(lastRequest.jsonp);
80390         }
80391     },
80392
80393     /**
80394      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
80395      * its own function so that it can be easily overridden.
80396      * @param {Ext.data.Model[]} records The records array
80397      * @return {String} The encoded records string
80398      */
80399     encodeRecords: function(records) {
80400         var encoded = "",
80401             i = 0,
80402             len = records.length;
80403
80404         for (; i < len; i++) {
80405             encoded += Ext.Object.toQueryString(records[i].data);
80406         }
80407
80408         return encoded;
80409     }
80410 });
80411
80412 /**
80413  * @author Ed Spencer
80414  *
80415  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
80416  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
80417  * save {@link Ext.data.Model model instances} for offline use.
80418  * @private
80419  */
80420 Ext.define('Ext.data.proxy.WebStorage', {
80421     extend: 'Ext.data.proxy.Client',
80422     alternateClassName: 'Ext.data.WebStorageProxy',
80423
80424     /**
80425      * @cfg {String} id
80426      * The unique ID used as the key in which all record data are stored in the local storage object.
80427      */
80428     id: undefined,
80429
80430     /**
80431      * Creates the proxy, throws an error if local storage is not supported in the current browser.
80432      * @param {Object} config (optional) Config object.
80433      */
80434     constructor: function(config) {
80435         this.callParent(arguments);
80436
80437         /**
80438          * @property {Object} cache
80439          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
80440          */
80441         this.cache = {};
80442
80443
80444         //if an id is not given, try to use the store's id instead
80445         this.id = this.id || (this.store ? this.store.storeId : undefined);
80446
80447
80448         this.initialize();
80449     },
80450
80451     //inherit docs
80452     create: function(operation, callback, scope) {
80453         var records = operation.records,
80454             length  = records.length,
80455             ids     = this.getIds(),
80456             id, record, i;
80457
80458         operation.setStarted();
80459
80460         for (i = 0; i < length; i++) {
80461             record = records[i];
80462
80463             if (record.phantom) {
80464                 record.phantom = false;
80465                 id = this.getNextId();
80466             } else {
80467                 id = record.getId();
80468             }
80469
80470             this.setRecord(record, id);
80471             ids.push(id);
80472         }
80473
80474         this.setIds(ids);
80475
80476         operation.setCompleted();
80477         operation.setSuccessful();
80478
80479         if (typeof callback == 'function') {
80480             callback.call(scope || this, operation);
80481         }
80482     },
80483
80484     //inherit docs
80485     read: function(operation, callback, scope) {
80486         //TODO: respect sorters, filters, start and limit options on the Operation
80487
80488         var records = [],
80489             ids     = this.getIds(),
80490             length  = ids.length,
80491             i, recordData, record;
80492
80493         //read a single record
80494         if (operation.id) {
80495             record = this.getRecord(operation.id);
80496
80497             if (record) {
80498                 records.push(record);
80499                 operation.setSuccessful();
80500             }
80501         } else {
80502             for (i = 0; i < length; i++) {
80503                 records.push(this.getRecord(ids[i]));
80504             }
80505             operation.setSuccessful();
80506         }
80507
80508         operation.setCompleted();
80509
80510         operation.resultSet = Ext.create('Ext.data.ResultSet', {
80511             records: records,
80512             total  : records.length,
80513             loaded : true
80514         });
80515
80516         if (typeof callback == 'function') {
80517             callback.call(scope || this, operation);
80518         }
80519     },
80520
80521     //inherit docs
80522     update: function(operation, callback, scope) {
80523         var records = operation.records,
80524             length  = records.length,
80525             ids     = this.getIds(),
80526             record, id, i;
80527
80528         operation.setStarted();
80529
80530         for (i = 0; i < length; i++) {
80531             record = records[i];
80532             this.setRecord(record);
80533
80534             //we need to update the set of ids here because it's possible that a non-phantom record was added
80535             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
80536             id = record.getId();
80537             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
80538                 ids.push(id);
80539             }
80540         }
80541         this.setIds(ids);
80542
80543         operation.setCompleted();
80544         operation.setSuccessful();
80545
80546         if (typeof callback == 'function') {
80547             callback.call(scope || this, operation);
80548         }
80549     },
80550
80551     //inherit
80552     destroy: function(operation, callback, scope) {
80553         var records = operation.records,
80554             length  = records.length,
80555             ids     = this.getIds(),
80556
80557             //newIds is a copy of ids, from which we remove the destroyed records
80558             newIds  = [].concat(ids),
80559             i;
80560
80561         for (i = 0; i < length; i++) {
80562             Ext.Array.remove(newIds, records[i].getId());
80563             this.removeRecord(records[i], false);
80564         }
80565
80566         this.setIds(newIds);
80567
80568         operation.setCompleted();
80569         operation.setSuccessful();
80570
80571         if (typeof callback == 'function') {
80572             callback.call(scope || this, operation);
80573         }
80574     },
80575
80576     /**
80577      * @private
80578      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
80579      * @param {String} id The record's unique ID
80580      * @return {Ext.data.Model} The model instance
80581      */
80582     getRecord: function(id) {
80583         if (this.cache[id] === undefined) {
80584             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
80585                 data    = {},
80586                 Model   = this.model,
80587                 fields  = Model.prototype.fields.items,
80588                 length  = fields.length,
80589                 i, field, name, record;
80590
80591             for (i = 0; i < length; i++) {
80592                 field = fields[i];
80593                 name  = field.name;
80594
80595                 if (typeof field.decode == 'function') {
80596                     data[name] = field.decode(rawData[name]);
80597                 } else {
80598                     data[name] = rawData[name];
80599                 }
80600             }
80601
80602             record = new Model(data, id);
80603             record.phantom = false;
80604
80605             this.cache[id] = record;
80606         }
80607
80608         return this.cache[id];
80609     },
80610
80611     /**
80612      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
80613      * @param {Ext.data.Model} record The model instance
80614      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
80615      */
80616     setRecord: function(record, id) {
80617         if (id) {
80618             record.setId(id);
80619         } else {
80620             id = record.getId();
80621         }
80622
80623         var me = this,
80624             rawData = record.data,
80625             data    = {},
80626             model   = me.model,
80627             fields  = model.prototype.fields.items,
80628             length  = fields.length,
80629             i = 0,
80630             field, name, obj, key;
80631
80632         for (; i < length; i++) {
80633             field = fields[i];
80634             name  = field.name;
80635
80636             if (typeof field.encode == 'function') {
80637                 data[name] = field.encode(rawData[name], record);
80638             } else {
80639                 data[name] = rawData[name];
80640             }
80641         }
80642
80643         obj = me.getStorageObject();
80644         key = me.getRecordKey(id);
80645
80646         //keep the cache up to date
80647         me.cache[id] = record;
80648
80649         //iPad bug requires that we remove the item before setting it
80650         obj.removeItem(key);
80651         obj.setItem(key, Ext.encode(data));
80652     },
80653
80654     /**
80655      * @private
80656      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
80657      * use instead because it updates the list of currently-stored record ids
80658      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
80659      */
80660     removeRecord: function(id, updateIds) {
80661         var me = this,
80662             ids;
80663
80664         if (id.isModel) {
80665             id = id.getId();
80666         }
80667
80668         if (updateIds !== false) {
80669             ids = me.getIds();
80670             Ext.Array.remove(ids, id);
80671             me.setIds(ids);
80672         }
80673
80674         me.getStorageObject().removeItem(me.getRecordKey(id));
80675     },
80676
80677     /**
80678      * @private
80679      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
80680      * storing data in the local storage object and should prevent naming collisions.
80681      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
80682      * @return {String} The unique key for this record
80683      */
80684     getRecordKey: function(id) {
80685         if (id.isModel) {
80686             id = id.getId();
80687         }
80688
80689         return Ext.String.format("{0}-{1}", this.id, id);
80690     },
80691
80692     /**
80693      * @private
80694      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
80695      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
80696      * @return {String} The counter key
80697      */
80698     getRecordCounterKey: function() {
80699         return Ext.String.format("{0}-counter", this.id);
80700     },
80701
80702     /**
80703      * @private
80704      * Returns the array of record IDs stored in this Proxy
80705      * @return {Number[]} The record IDs. Each is cast as a Number
80706      */
80707     getIds: function() {
80708         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
80709             length = ids.length,
80710             i;
80711
80712         if (length == 1 && ids[0] === "") {
80713             ids = [];
80714         } else {
80715             for (i = 0; i < length; i++) {
80716                 ids[i] = parseInt(ids[i], 10);
80717             }
80718         }
80719
80720         return ids;
80721     },
80722
80723     /**
80724      * @private
80725      * Saves the array of ids representing the set of all records in the Proxy
80726      * @param {Number[]} ids The ids to set
80727      */
80728     setIds: function(ids) {
80729         var obj = this.getStorageObject(),
80730             str = ids.join(",");
80731
80732         obj.removeItem(this.id);
80733
80734         if (!Ext.isEmpty(str)) {
80735             obj.setItem(this.id, str);
80736         }
80737     },
80738
80739     /**
80740      * @private
80741      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
80742      * Increments the counter.
80743      * @return {Number} The id
80744      */
80745     getNextId: function() {
80746         var obj  = this.getStorageObject(),
80747             key  = this.getRecordCounterKey(),
80748             last = obj.getItem(key),
80749             ids, id;
80750
80751         if (last === null) {
80752             ids = this.getIds();
80753             last = ids[ids.length - 1] || 0;
80754         }
80755
80756         id = parseInt(last, 10) + 1;
80757         obj.setItem(key, id);
80758
80759         return id;
80760     },
80761
80762     /**
80763      * @private
80764      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
80765      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
80766      */
80767     initialize: function() {
80768         var storageObject = this.getStorageObject();
80769         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
80770     },
80771
80772     /**
80773      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
80774      * storage object.
80775      */
80776     clear: function() {
80777         var obj = this.getStorageObject(),
80778             ids = this.getIds(),
80779             len = ids.length,
80780             i;
80781
80782         //remove all the records
80783         for (i = 0; i < len; i++) {
80784             this.removeRecord(ids[i]);
80785         }
80786
80787         //remove the supporting objects
80788         obj.removeItem(this.getRecordCounterKey());
80789         obj.removeItem(this.id);
80790     },
80791
80792     /**
80793      * @private
80794      * Abstract function which should return the storage object that data will be saved to. This must be implemented
80795      * in each subclass.
80796      * @return {Object} The storage object
80797      */
80798     getStorageObject: function() {
80799     }
80800 });
80801 /**
80802  * @author Ed Spencer
80803  *
80804  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
80805  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
80806  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
80807  *
80808  * localStorage is extremely useful for saving user-specific information without needing to build server-side
80809  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
80810  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
80811  *
80812  *     Ext.define('Search', {
80813  *         fields: ['id', 'query'],
80814  *         extend: 'Ext.data.Model',
80815  *         proxy: {
80816  *             type: 'localstorage',
80817  *             id  : 'twitter-Searches'
80818  *         }
80819  *     });
80820  *
80821  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
80822  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
80823  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
80824  * LocalStorageProxy to manage the saved Search data.
80825  *
80826  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
80827  *
80828  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
80829  *     var store = Ext.create('Ext.data.Store', {
80830  *         model: "Search"
80831  *     });
80832  *
80833  *     //loads any existing Search data from localStorage
80834  *     store.load();
80835  *
80836  *     //now add some Searches
80837  *     store.add({query: 'Sencha Touch'});
80838  *     store.add({query: 'Ext JS'});
80839  *
80840  *     //finally, save our Search data to localStorage
80841  *     store.sync();
80842  *
80843  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
80844  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
80845  *
80846  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
80847  *
80848  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
80849  *     search.save();
80850  *
80851  * # Limitations
80852  *
80853  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
80854  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
80855  * object.
80856  *
80857  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
80858  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
80859  */
80860 Ext.define('Ext.data.proxy.LocalStorage', {
80861     extend: 'Ext.data.proxy.WebStorage',
80862     alias: 'proxy.localstorage',
80863     alternateClassName: 'Ext.data.LocalStorageProxy',
80864     
80865     //inherit docs
80866     getStorageObject: function() {
80867         return window.localStorage;
80868     }
80869 });
80870 /**
80871  * @author Ed Spencer
80872  *
80873  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
80874  * every page refresh.
80875  *
80876  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
80877  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
80878  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
80879  * Store:
80880  *
80881  *     //this is the model we will be using in the store
80882  *     Ext.define('User', {
80883  *         extend: 'Ext.data.Model',
80884  *         fields: [
80885  *             {name: 'id',    type: 'int'},
80886  *             {name: 'name',  type: 'string'},
80887  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
80888  *         ]
80889  *     });
80890  *
80891  *     //this data does not line up to our model fields - the phone field is called phoneNumber
80892  *     var data = {
80893  *         users: [
80894  *             {
80895  *                 id: 1,
80896  *                 name: 'Ed Spencer',
80897  *                 phoneNumber: '555 1234'
80898  *             },
80899  *             {
80900  *                 id: 2,
80901  *                 name: 'Abe Elias',
80902  *                 phoneNumber: '666 1234'
80903  *             }
80904  *         ]
80905  *     };
80906  *
80907  *     //note how we set the 'root' in the reader to match the data structure above
80908  *     var store = Ext.create('Ext.data.Store', {
80909  *         autoLoad: true,
80910  *         model: 'User',
80911  *         data : data,
80912  *         proxy: {
80913  *             type: 'memory',
80914  *             reader: {
80915  *                 type: 'json',
80916  *                 root: 'users'
80917  *             }
80918  *         }
80919  *     });
80920  */
80921 Ext.define('Ext.data.proxy.Memory', {
80922     extend: 'Ext.data.proxy.Client',
80923     alias: 'proxy.memory',
80924     alternateClassName: 'Ext.data.MemoryProxy',
80925
80926     /**
80927      * @cfg {Ext.data.Model[]} data
80928      * Optional array of Records to load into the Proxy
80929      */
80930
80931     constructor: function(config) {
80932         this.callParent([config]);
80933
80934         //ensures that the reader has been instantiated properly
80935         this.setReader(this.reader);
80936     },
80937
80938     /**
80939      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
80940      * @param {Ext.data.Operation} operation The read Operation
80941      * @param {Function} callback The callback to call when reading has completed
80942      * @param {Object} scope The scope to call the callback function in
80943      */
80944     read: function(operation, callback, scope) {
80945         var me     = this,
80946             reader = me.getReader(),
80947             result = reader.read(me.data);
80948
80949         Ext.apply(operation, {
80950             resultSet: result
80951         });
80952
80953         operation.setCompleted();
80954         operation.setSuccessful();
80955         Ext.callback(callback, scope || me, [operation]);
80956     },
80957
80958     clear: Ext.emptyFn
80959 });
80960
80961 /**
80962  * @author Ed Spencer
80963  *
80964  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
80965  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
80966  * with an inline Rest proxy
80967  *
80968  *     Ext.define('User', {
80969  *         extend: 'Ext.data.Model',
80970  *         fields: ['id', 'name', 'email'],
80971  *
80972  *         proxy: {
80973  *             type: 'rest',
80974  *             url : '/users'
80975  *         }
80976  *     });
80977  *
80978  * 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
80979  * request to '/users':
80980  *
80981  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
80982  *
80983  *     user.save(); //POST /users
80984  *
80985  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
80986  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
80987  *
80988  *     user.save({
80989  *         success: function(user) {
80990  *             user.set('name', 'Khan Noonien Singh');
80991  *
80992  *             user.save(); //PUT /users/123
80993  *         }
80994  *     });
80995  *
80996  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
80997  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
80998  *
80999  *         user.destroy(); //DELETE /users/123
81000  *
81001  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
81002  *
81003  *     //1. Load via Store
81004  *
81005  *     //the Store automatically picks up the Proxy from the User model
81006  *     var store = Ext.create('Ext.data.Store', {
81007  *         model: 'User'
81008  *     });
81009  *
81010  *     store.load(); //GET /users
81011  *
81012  *     //2. Load directly from the Model
81013  *
81014  *     //GET /users/123
81015  *     Ext.ModelManager.getModel('User').load(123, {
81016  *         success: function(user) {
81017  *             console.log(user.getId()); //outputs 123
81018  *         }
81019  *     });
81020  *
81021  * # Url generation
81022  *
81023  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
81024  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
81025  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
81026  *
81027  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
81028  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
81029  *
81030  *     new Ext.data.proxy.Rest({
81031  *         url: '/users',
81032  *         appendId: true //default
81033  *     });
81034  *
81035  *     // Collection url: /users
81036  *     // Instance url  : /users/123
81037  *
81038  * The Rest proxy can also optionally append a format string to the end of any generated url:
81039  *
81040  *     new Ext.data.proxy.Rest({
81041  *         url: '/users',
81042  *         format: 'json'
81043  *     });
81044  *
81045  *     // Collection url: /users.json
81046  *     // Instance url  : /users/123.json
81047  *
81048  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
81049  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
81050  * an example of how to achieve this.
81051  *
81052  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
81053  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
81054  * details.
81055  *
81056  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
81057  */
81058 Ext.define('Ext.data.proxy.Rest', {
81059     extend: 'Ext.data.proxy.Ajax',
81060     alternateClassName: 'Ext.data.RestProxy',
81061     alias : 'proxy.rest',
81062     
81063     /**
81064      * @cfg {Boolean} appendId
81065      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
81066      * See Rest proxy intro docs for more details. Defaults to true.
81067      */
81068     appendId: true,
81069     
81070     /**
81071      * @cfg {String} format
81072      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
81073      * for full details. Defaults to undefined.
81074      */
81075     
81076     /**
81077      * @cfg {Boolean} batchActions
81078      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
81079      */
81080     batchActions: false,
81081     
81082     /**
81083      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
81084      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
81085      * that additional parameters like the cache buster string are appended.
81086      * @param {Object} request
81087      */
81088     buildUrl: function(request) {
81089         var me        = this,
81090             operation = request.operation,
81091             records   = operation.records || [],
81092             record    = records[0],
81093             format    = me.format,
81094             url       = me.getUrl(request),
81095             id        = record ? record.getId() : operation.id;
81096         
81097         if (me.appendId && id) {
81098             if (!url.match(/\/$/)) {
81099                 url += '/';
81100             }
81101             
81102             url += id;
81103         }
81104         
81105         if (format) {
81106             if (!url.match(/\.$/)) {
81107                 url += '.';
81108             }
81109             
81110             url += format;
81111         }
81112         
81113         request.url = url;
81114         
81115         return me.callParent(arguments);
81116     }
81117 }, function() {
81118     Ext.apply(this.prototype, {
81119         /**
81120          * @property {Object} actionMethods
81121          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
81122          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
81123          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
81124          * can be overridden instead.
81125          */
81126         actionMethods: {
81127             create : 'POST',
81128             read   : 'GET',
81129             update : 'PUT',
81130             destroy: 'DELETE'
81131         }
81132     });
81133 });
81134
81135 /**
81136  * @author Ed Spencer
81137  *
81138  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
81139  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
81140  * unique ID which is used as a key in which all record data are stored in the session storage object.
81141  *
81142  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81143  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81144  *
81145  * Proxies are almost always used with a {@link Ext.data.Store store}:
81146  *
81147  *     new Ext.data.Store({
81148  *         proxy: {
81149  *             type: 'sessionstorage',
81150  *             id  : 'myProxyKey'
81151  *         }
81152  *     });
81153  *
81154  * Alternatively you can instantiate the Proxy directly:
81155  *
81156  *     new Ext.data.proxy.SessionStorage({
81157  *         id  : 'myOtherProxyKey'
81158  *     });
81159  *
81160  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
81161  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
81162  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
81163  */
81164 Ext.define('Ext.data.proxy.SessionStorage', {
81165     extend: 'Ext.data.proxy.WebStorage',
81166     alias: 'proxy.sessionstorage',
81167     alternateClassName: 'Ext.data.SessionStorageProxy',
81168     
81169     //inherit docs
81170     getStorageObject: function() {
81171         return window.sessionStorage;
81172     }
81173 });
81174
81175 /**
81176  * @author Ed Spencer
81177  * @class Ext.data.reader.Array
81178  * @extends Ext.data.reader.Json
81179  * 
81180  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
81181  * Each element of that Array represents a row of data fields. The
81182  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
81183  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
81184  * 
81185  * <p><u>Example code:</u></p>
81186  * 
81187 <pre><code>
81188 Employee = Ext.define('Employee', {
81189     extend: 'Ext.data.Model',
81190     fields: [
81191         'id',
81192         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
81193         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
81194     ]
81195 });
81196
81197 var myReader = new Ext.data.reader.Array({
81198     model: 'Employee'
81199 }, Employee);
81200 </code></pre>
81201  * 
81202  * <p>This would consume an Array like this:</p>
81203  * 
81204 <pre><code>
81205 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
81206 </code></pre>
81207  * 
81208  * @constructor
81209  * Create a new ArrayReader
81210  * @param {Object} meta Metadata configuration options.
81211  */
81212 Ext.define('Ext.data.reader.Array', {
81213     extend: 'Ext.data.reader.Json',
81214     alternateClassName: 'Ext.data.ArrayReader',
81215     alias : 'reader.array',
81216
81217     /**
81218      * @private
81219      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
81220      * reference the correct position in the array.
81221      */
81222     buildExtractors: function() {
81223         this.callParent(arguments);
81224         
81225         var fields = this.model.prototype.fields.items,
81226             i = 0,
81227             length = fields.length,
81228             extractorFunctions = [],
81229             map;
81230         
81231         for (; i < length; i++) {
81232             map = fields[i].mapping;
81233             extractorFunctions.push(function(index) {
81234                 return function(data) {
81235                     return data[index];
81236                 };
81237             }(map !== null ? map : i));
81238         }
81239         
81240         this.extractorFunctions = extractorFunctions;
81241     }
81242 });
81243
81244 /**
81245  * @author Ed Spencer
81246  * @class Ext.data.reader.Xml
81247  * @extends Ext.data.reader.Reader
81248  *
81249  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
81250  * happens as a result of loading a Store - for example we might create something like this:</p>
81251  *
81252 <pre><code>
81253 Ext.define('User', {
81254     extend: 'Ext.data.Model',
81255     fields: ['id', 'name', 'email']
81256 });
81257
81258 var store = Ext.create('Ext.data.Store', {
81259     model: 'User',
81260     proxy: {
81261         type: 'ajax',
81262         url : 'users.xml',
81263         reader: {
81264             type: 'xml',
81265             record: 'user'
81266         }
81267     }
81268 });
81269 </code></pre>
81270  *
81271  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
81272  * not already familiar with them.</p>
81273  *
81274  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
81275  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
81276  * Store, so it is as if we passed this instead:
81277  *
81278 <pre><code>
81279 reader: {
81280     type : 'xml',
81281     model: 'User',
81282     record: 'user'
81283 }
81284 </code></pre>
81285  *
81286  * <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>
81287  *
81288 <pre><code>
81289 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81290 &lt;user&gt;
81291     &lt;id&gt;1&lt;/id&gt;
81292     &lt;name&gt;Ed Spencer&lt;/name&gt;
81293     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81294 &lt;/user&gt;
81295 &lt;user&gt;
81296     &lt;id&gt;2&lt;/id&gt;
81297     &lt;name&gt;Abe Elias&lt;/name&gt;
81298     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81299 &lt;/user&gt;
81300 </code></pre>
81301  *
81302  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
81303  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
81304  *
81305  * <p><u>Reading other XML formats</u></p>
81306  *
81307  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
81308  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
81309  * {@link #root} configuration to parse data that comes back like this:</p>
81310  *
81311 <pre><code>
81312 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81313 &lt;users&gt;
81314     &lt;user&gt;
81315         &lt;id&gt;1&lt;/id&gt;
81316         &lt;name&gt;Ed Spencer&lt;/name&gt;
81317         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81318     &lt;/user&gt;
81319     &lt;user&gt;
81320         &lt;id&gt;2&lt;/id&gt;
81321         &lt;name&gt;Abe Elias&lt;/name&gt;
81322         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81323     &lt;/user&gt;
81324 &lt;/users&gt;
81325 </code></pre>
81326  *
81327  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
81328  *
81329 <pre><code>
81330 reader: {
81331     type  : 'xml',
81332     root  : 'users',
81333     record: 'user'
81334 }
81335 </code></pre>
81336  *
81337  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
81338  * a larger structure, so a response like this will still work:
81339  *
81340 <pre><code>
81341 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81342 &lt;deeply&gt;
81343     &lt;nested&gt;
81344         &lt;xml&gt;
81345             &lt;users&gt;
81346                 &lt;user&gt;
81347                     &lt;id&gt;1&lt;/id&gt;
81348                     &lt;name&gt;Ed Spencer&lt;/name&gt;
81349                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81350                 &lt;/user&gt;
81351                 &lt;user&gt;
81352                     &lt;id&gt;2&lt;/id&gt;
81353                     &lt;name&gt;Abe Elias&lt;/name&gt;
81354                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81355                 &lt;/user&gt;
81356             &lt;/users&gt;
81357         &lt;/xml&gt;
81358     &lt;/nested&gt;
81359 &lt;/deeply&gt;
81360 </code></pre>
81361  *
81362  * <p><u>Response metadata</u></p>
81363  *
81364  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
81365  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
81366  * like this:</p>
81367  *
81368 <pre><code>
81369 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81370 &lt;total&gt;100&lt;/total&gt;
81371 &lt;success&gt;true&lt;/success&gt;
81372 &lt;users&gt;
81373     &lt;user&gt;
81374         &lt;id&gt;1&lt;/id&gt;
81375         &lt;name&gt;Ed Spencer&lt;/name&gt;
81376         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81377     &lt;/user&gt;
81378     &lt;user&gt;
81379         &lt;id&gt;2&lt;/id&gt;
81380         &lt;name&gt;Abe Elias&lt;/name&gt;
81381         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81382     &lt;/user&gt;
81383 &lt;/users&gt;
81384 </code></pre>
81385  *
81386  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
81387  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
81388  * options:</p>
81389  *
81390 <pre><code>
81391 reader: {
81392     type: 'xml',
81393     root: 'users',
81394     totalProperty  : 'total',
81395     successProperty: 'success'
81396 }
81397 </code></pre>
81398  *
81399  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
81400  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
81401  * returned.</p>
81402  *
81403  * <p><u>Response format</u></p>
81404  *
81405  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
81406  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
81407  * work correctly otherwise.</p>
81408  */
81409 Ext.define('Ext.data.reader.Xml', {
81410     extend: 'Ext.data.reader.Reader',
81411     alternateClassName: 'Ext.data.XmlReader',
81412     alias : 'reader.xml',
81413
81414     /**
81415      * @cfg {String} record (required)
81416      * The DomQuery path to the repeated element which contains record information.
81417      */
81418
81419     /**
81420      * @private
81421      * Creates a function to return some particular key of data from a response. The totalProperty and
81422      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
81423      * @param {String} key
81424      * @return {Function}
81425      */
81426     createAccessor: function(expr) {
81427         var me = this;
81428
81429         if (Ext.isEmpty(expr)) {
81430             return Ext.emptyFn;
81431         }
81432
81433         if (Ext.isFunction(expr)) {
81434             return expr;
81435         }
81436
81437         return function(root) {
81438             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
81439         };
81440     },
81441
81442     getNodeValue: function(node) {
81443         if (node && node.firstChild) {
81444             return node.firstChild.nodeValue;
81445         }
81446         return undefined;
81447     },
81448
81449     //inherit docs
81450     getResponseData: function(response) {
81451         var xml = response.responseXML;
81452
81453
81454         return xml;
81455     },
81456
81457     /**
81458      * Normalizes the data object
81459      * @param {Object} data The raw data object
81460      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
81461      */
81462     getData: function(data) {
81463         return data.documentElement || data;
81464     },
81465
81466     /**
81467      * @private
81468      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
81469      * @param {Object} data The XML data object
81470      * @return {XMLElement} The root node element
81471      */
81472     getRoot: function(data) {
81473         var nodeName = data.nodeName,
81474             root     = this.root;
81475
81476         if (!root || (nodeName && nodeName == root)) {
81477             return data;
81478         } else if (Ext.DomQuery.isXml(data)) {
81479             // This fix ensures we have XML data
81480             // Related to TreeStore calling getRoot with the root node, which isn't XML
81481             // Probably should be resolved in TreeStore at some point
81482             return Ext.DomQuery.selectNode(root, data);
81483         }
81484     },
81485
81486     /**
81487      * @private
81488      * We're just preparing the data for the superclass by pulling out the record nodes we want
81489      * @param {XMLElement} root The XML root node
81490      * @return {Ext.data.Model[]} The records
81491      */
81492     extractData: function(root) {
81493         var recordName = this.record;
81494
81495
81496         if (recordName != root.nodeName) {
81497             root = Ext.DomQuery.select(recordName, root);
81498         } else {
81499             root = [root];
81500         }
81501         return this.callParent([root]);
81502     },
81503
81504     /**
81505      * @private
81506      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
81507      * @param {Object} data The raw data object
81508      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
81509      * @return {XMLElement} The root
81510      */
81511     getAssociatedDataRoot: function(data, associationName) {
81512         return Ext.DomQuery.select(associationName, data)[0];
81513     },
81514
81515     /**
81516      * Parses an XML document and returns a ResultSet containing the model instances
81517      * @param {Object} doc Parsed XML document
81518      * @return {Ext.data.ResultSet} The parsed result set
81519      */
81520     readRecords: function(doc) {
81521         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
81522         if (Ext.isArray(doc)) {
81523             doc = doc[0];
81524         }
81525
81526         /**
81527          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
81528          * @property xmlData
81529          * @type Object
81530          */
81531         this.xmlData = doc;
81532         return this.callParent([doc]);
81533     }
81534 });
81535 /**
81536  * @author Ed Spencer
81537  * @class Ext.data.writer.Xml
81538  * @extends Ext.data.writer.Writer
81539
81540 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
81541 The {@link #documentRoot} property is used to specify the root element in the XML document.
81542 The {@link #record} option is used to specify the element name for each record that will make
81543 up the XML document.
81544
81545  * @markdown
81546  */
81547 Ext.define('Ext.data.writer.Xml', {
81548     
81549     /* Begin Definitions */
81550     
81551     extend: 'Ext.data.writer.Writer',
81552     alternateClassName: 'Ext.data.XmlWriter',
81553     
81554     alias: 'writer.xml',
81555     
81556     /* End Definitions */
81557     
81558     /**
81559      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
81560      * If there is more than 1 record and the root is not specified, the default document root will still be used
81561      * to ensure a valid XML document is created.
81562      */
81563     documentRoot: 'xmlData',
81564     
81565     /**
81566      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
81567      * to form a valid XML document.
81568      */
81569     defaultDocumentRoot: 'xmlData',
81570
81571     /**
81572      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
81573      * Defaults to <tt>''</tt>.
81574      */
81575     header: '',
81576
81577     /**
81578      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
81579      */
81580     record: 'record',
81581
81582     //inherit docs
81583     writeRecords: function(request, data) {
81584         var me = this,
81585             xml = [],
81586             i = 0,
81587             len = data.length,
81588             root = me.documentRoot,
81589             record = me.record,
81590             needsRoot = data.length !== 1,
81591             item,
81592             key;
81593             
81594         // may not exist
81595         xml.push(me.header || '');
81596         
81597         if (!root && needsRoot) {
81598             root = me.defaultDocumentRoot;
81599         }
81600         
81601         if (root) {
81602             xml.push('<', root, '>');
81603         }
81604             
81605         for (; i < len; ++i) {
81606             item = data[i];
81607             xml.push('<', record, '>');
81608             for (key in item) {
81609                 if (item.hasOwnProperty(key)) {
81610                     xml.push('<', key, '>', item[key], '</', key, '>');
81611                 }
81612             }
81613             xml.push('</', record, '>');
81614         }
81615         
81616         if (root) {
81617             xml.push('</', root, '>');
81618         }
81619             
81620         request.xmlData = xml.join('');
81621         return request;
81622     }
81623 });
81624
81625 /**
81626  * @class Ext.direct.Event
81627  * A base class for all Ext.direct events. An event is
81628  * created after some kind of interaction with the server.
81629  * The event class is essentially just a data structure
81630  * to hold a Direct response.
81631  */
81632 Ext.define('Ext.direct.Event', {
81633
81634     /* Begin Definitions */
81635
81636     alias: 'direct.event',
81637
81638     requires: ['Ext.direct.Manager'],
81639
81640     /* End Definitions */
81641
81642     status: true,
81643
81644     /**
81645      * Creates new Event.
81646      * @param {Object} config (optional) Config object.
81647      */
81648     constructor: function(config) {
81649         Ext.apply(this, config);
81650     },
81651
81652     /**
81653      * Return the raw data for this event.
81654      * @return {Object} The data from the event
81655      */
81656     getData: function(){
81657         return this.data;
81658     }
81659 });
81660
81661 /**
81662  * @class Ext.direct.RemotingEvent
81663  * @extends Ext.direct.Event
81664  * An event that is fired when data is received from a 
81665  * {@link Ext.direct.RemotingProvider}. Contains a method to the
81666  * related transaction for the direct request, see {@link #getTransaction}
81667  */
81668 Ext.define('Ext.direct.RemotingEvent', {
81669     
81670     /* Begin Definitions */
81671    
81672     extend: 'Ext.direct.Event',
81673     
81674     alias: 'direct.rpc',
81675     
81676     /* End Definitions */
81677     
81678     /**
81679      * Get the transaction associated with this event.
81680      * @return {Ext.direct.Transaction} The transaction
81681      */
81682     getTransaction: function(){
81683         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
81684     }
81685 });
81686
81687 /**
81688  * @class Ext.direct.ExceptionEvent
81689  * @extends Ext.direct.RemotingEvent
81690  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
81691  */
81692 Ext.define('Ext.direct.ExceptionEvent', {
81693     
81694     /* Begin Definitions */
81695    
81696     extend: 'Ext.direct.RemotingEvent',
81697     
81698     alias: 'direct.exception',
81699     
81700     /* End Definitions */
81701    
81702    status: false
81703 });
81704
81705 /**
81706  * @class Ext.direct.Provider
81707  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
81708  *
81709  * <p>For example Ext JS implements the following subclasses:</p>
81710  * <pre><code>
81711 Provider
81712 |
81713 +---{@link Ext.direct.JsonProvider JsonProvider}
81714     |
81715     +---{@link Ext.direct.PollingProvider PollingProvider}
81716     |
81717     +---{@link Ext.direct.RemotingProvider RemotingProvider}
81718  * </code></pre>
81719  * @abstract
81720  */
81721 Ext.define('Ext.direct.Provider', {
81722
81723     /* Begin Definitions */
81724
81725    alias: 'direct.provider',
81726
81727     mixins: {
81728         observable: 'Ext.util.Observable'
81729     },
81730
81731     /* End Definitions */
81732
81733    /**
81734      * @cfg {String} id
81735      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
81736      * You should assign an id if you need to be able to access the provider later and you do
81737      * not have an object reference available, for example:
81738      * <pre><code>
81739 Ext.direct.Manager.addProvider({
81740     type: 'polling',
81741     url:  'php/poll.php',
81742     id:   'poll-provider'
81743 });
81744 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
81745 p.disconnect();
81746      * </code></pre>
81747      */
81748
81749     constructor : function(config){
81750         var me = this;
81751
81752         Ext.apply(me, config);
81753         me.addEvents(
81754             /**
81755              * @event connect
81756              * Fires when the Provider connects to the server-side
81757              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81758              */
81759             'connect',
81760             /**
81761              * @event disconnect
81762              * Fires when the Provider disconnects from the server-side
81763              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81764              */
81765             'disconnect',
81766             /**
81767              * @event data
81768              * Fires when the Provider receives data from the server-side
81769              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81770              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
81771              */
81772             'data',
81773             /**
81774              * @event exception
81775              * Fires when the Provider receives an exception from the server-side
81776              */
81777             'exception'
81778         );
81779         me.mixins.observable.constructor.call(me, config);
81780     },
81781
81782     /**
81783      * Returns whether or not the server-side is currently connected.
81784      * Abstract method for subclasses to implement.
81785      */
81786     isConnected: function(){
81787         return false;
81788     },
81789
81790     /**
81791      * Abstract methods for subclasses to implement.
81792      * @method
81793      */
81794     connect: Ext.emptyFn,
81795
81796     /**
81797      * Abstract methods for subclasses to implement.
81798      * @method
81799      */
81800     disconnect: Ext.emptyFn
81801 });
81802
81803 /**
81804  * @class Ext.direct.JsonProvider
81805  * @extends Ext.direct.Provider
81806
81807 A base provider for communicating using JSON. This is an abstract class
81808 and should not be instanced directly.
81809
81810  * @markdown
81811  * @abstract
81812  */
81813
81814 Ext.define('Ext.direct.JsonProvider', {
81815
81816     /* Begin Definitions */
81817
81818     extend: 'Ext.direct.Provider',
81819
81820     alias: 'direct.jsonprovider',
81821
81822     uses: ['Ext.direct.ExceptionEvent'],
81823
81824     /* End Definitions */
81825
81826    /**
81827     * Parse the JSON response
81828     * @private
81829     * @param {Object} response The XHR response object
81830     * @return {Object} The data in the response.
81831     */
81832    parseResponse: function(response){
81833         if (!Ext.isEmpty(response.responseText)) {
81834             if (Ext.isObject(response.responseText)) {
81835                 return response.responseText;
81836             }
81837             return Ext.decode(response.responseText);
81838         }
81839         return null;
81840     },
81841
81842     /**
81843      * Creates a set of events based on the XHR response
81844      * @private
81845      * @param {Object} response The XHR response
81846      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
81847      */
81848     createEvents: function(response){
81849         var data = null,
81850             events = [],
81851             event,
81852             i = 0,
81853             len;
81854
81855         try{
81856             data = this.parseResponse(response);
81857         } catch(e) {
81858             event = Ext.create('Ext.direct.ExceptionEvent', {
81859                 data: e,
81860                 xhr: response,
81861                 code: Ext.direct.Manager.self.exceptions.PARSE,
81862                 message: 'Error parsing json response: \n\n ' + data
81863             });
81864             return [event];
81865         }
81866
81867         if (Ext.isArray(data)) {
81868             for (len = data.length; i < len; ++i) {
81869                 events.push(this.createEvent(data[i]));
81870             }
81871         } else {
81872             events.push(this.createEvent(data));
81873         }
81874         return events;
81875     },
81876
81877     /**
81878      * Create an event from a response object
81879      * @param {Object} response The XHR response object
81880      * @return {Ext.direct.Event} The event
81881      */
81882     createEvent: function(response){
81883         return Ext.create('direct.' + response.type, response);
81884     }
81885 });
81886 /**
81887  * @class Ext.direct.PollingProvider
81888  * @extends Ext.direct.JsonProvider
81889  *
81890  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
81891  * The initial request for data originates from the client, and then is responded to by the
81892  * server.</p>
81893  * 
81894  * <p>All configurations for the PollingProvider should be generated by the server-side
81895  * API portion of the Ext.Direct stack.</p>
81896  *
81897  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
81898  * specifying <tt>type = 'polling'</tt>.  For example:</p>
81899  * <pre><code>
81900 var pollA = new Ext.direct.PollingProvider({
81901     type:'polling',
81902     url: 'php/pollA.php',
81903 });
81904 Ext.direct.Manager.addProvider(pollA);
81905 pollA.disconnect();
81906
81907 Ext.direct.Manager.addProvider(
81908     {
81909         type:'polling',
81910         url: 'php/pollB.php',
81911         id: 'pollB-provider'
81912     }
81913 );
81914 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
81915  * </code></pre>
81916  */
81917 Ext.define('Ext.direct.PollingProvider', {
81918     
81919     /* Begin Definitions */
81920     
81921     extend: 'Ext.direct.JsonProvider',
81922     
81923     alias: 'direct.pollingprovider',
81924     
81925     uses: ['Ext.direct.ExceptionEvent'],
81926     
81927     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
81928     
81929     /* End Definitions */
81930     
81931     /**
81932      * @cfg {Number} interval
81933      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
81934      */
81935     interval: 3000,
81936
81937     /**
81938      * @cfg {Object} baseParams
81939      * An object containing properties which are to be sent as parameters on every polling request
81940      */
81941     
81942     /**
81943      * @cfg {String/Function} url
81944      * The url which the PollingProvider should contact with each request. This can also be
81945      * an imported Ext.Direct method which will accept the baseParams as its only argument.
81946      */
81947
81948     // private
81949     constructor : function(config){
81950         this.callParent(arguments);
81951         this.addEvents(
81952             /**
81953              * @event beforepoll
81954              * Fired immediately before a poll takes place, an event handler can return false
81955              * in order to cancel the poll.
81956              * @param {Ext.direct.PollingProvider} this
81957              */
81958             'beforepoll',            
81959             /**
81960              * @event poll
81961              * This event has not yet been implemented.
81962              * @param {Ext.direct.PollingProvider} this
81963              */
81964             'poll'
81965         );
81966     },
81967
81968     // inherited
81969     isConnected: function(){
81970         return !!this.pollTask;
81971     },
81972
81973     /**
81974      * Connect to the server-side and begin the polling process. To handle each
81975      * response subscribe to the data event.
81976      */
81977     connect: function(){
81978         var me = this, url = me.url;
81979         
81980         if (url && !me.pollTask) {
81981             me.pollTask = Ext.TaskManager.start({
81982                 run: function(){
81983                     if (me.fireEvent('beforepoll', me) !== false) {
81984                         if (Ext.isFunction(url)) {
81985                             url(me.baseParams);
81986                         } else {
81987                             Ext.Ajax.request({
81988                                 url: url,
81989                                 callback: me.onData,
81990                                 scope: me,
81991                                 params: me.baseParams
81992                             });
81993                         }
81994                     }
81995                 },
81996                 interval: me.interval,
81997                 scope: me
81998             });
81999             me.fireEvent('connect', me);
82000         } else if (!url) {
82001         }
82002     },
82003
82004     /**
82005      * Disconnect from the server-side and stop the polling process. The disconnect
82006      * event will be fired on a successful disconnect.
82007      */
82008     disconnect: function(){
82009         var me = this;
82010         
82011         if (me.pollTask) {
82012             Ext.TaskManager.stop(me.pollTask);
82013             delete me.pollTask;
82014             me.fireEvent('disconnect', me);
82015         }
82016     },
82017
82018     // private
82019     onData: function(opt, success, response){
82020         var me = this, 
82021             i = 0, 
82022             len,
82023             events;
82024         
82025         if (success) {
82026             events = me.createEvents(response);
82027             for (len = events.length; i < len; ++i) {
82028                 me.fireEvent('data', me, events[i]);
82029             }
82030         } else {
82031             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
82032                 data: null,
82033                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82034                 message: 'Unable to connect to the server.',
82035                 xhr: response
82036             }));
82037         }
82038     }
82039 });
82040 /**
82041  * Small utility class used internally to represent a Direct method.
82042  * @class Ext.direct.RemotingMethod
82043  * @ignore
82044  */
82045 Ext.define('Ext.direct.RemotingMethod', {
82046
82047     constructor: function(config){
82048         var me = this,
82049             params = Ext.isDefined(config.params) ? config.params : config.len,
82050             name;
82051
82052         me.name = config.name;
82053         me.formHandler = config.formHandler;
82054         if (Ext.isNumber(params)) {
82055             // given only the number of parameters
82056             me.len = params;
82057             me.ordered = true;
82058         } else {
82059             /*
82060              * Given an array of either
82061              * a) String
82062              * b) Objects with a name property. We may want to encode extra info in here later
82063              */
82064             me.params = [];
82065             Ext.each(params, function(param){
82066                 name = Ext.isObject(param) ? param.name : param;
82067                 me.params.push(name);
82068             });
82069         }
82070     },
82071
82072     /**
82073      * Takes the arguments for the Direct function and splits the arguments
82074      * from the scope and the callback.
82075      * @param {Array} args The arguments passed to the direct call
82076      * @return {Object} An object with 3 properties, args, callback & scope.
82077      */
82078     getCallData: function(args){
82079         var me = this,
82080             data = null,
82081             len  = me.len,
82082             params = me.params,
82083             callback,
82084             scope,
82085             name;
82086
82087         if (me.ordered) {
82088             callback = args[len];
82089             scope = args[len + 1];
82090             if (len !== 0) {
82091                 data = args.slice(0, len);
82092             }
82093         } else {
82094             data = Ext.apply({}, args[0]);
82095             callback = args[1];
82096             scope = args[2];
82097
82098             // filter out any non-existent properties
82099             for (name in data) {
82100                 if (data.hasOwnProperty(name)) {
82101                     if (!Ext.Array.contains(params, name)) {
82102                         delete data[name];
82103                     }
82104                 }
82105             }
82106         }
82107
82108         return {
82109             data: data,
82110             callback: callback,
82111             scope: scope
82112         };
82113     }
82114 });
82115
82116 /**
82117  * Supporting Class for Ext.Direct (not intended to be used directly).
82118  */
82119 Ext.define('Ext.direct.Transaction', {
82120     
82121     /* Begin Definitions */
82122    
82123     alias: 'direct.transaction',
82124     alternateClassName: 'Ext.Direct.Transaction',
82125    
82126     statics: {
82127         TRANSACTION_ID: 0
82128     },
82129    
82130     /* End Definitions */
82131
82132     /**
82133      * Creates new Transaction.
82134      * @param {Object} [config] Config object.
82135      */
82136     constructor: function(config){
82137         var me = this;
82138         
82139         Ext.apply(me, config);
82140         me.id = ++me.self.TRANSACTION_ID;
82141         me.retryCount = 0;
82142     },
82143    
82144     send: function(){
82145          this.provider.queueTransaction(this);
82146     },
82147
82148     retry: function(){
82149         this.retryCount++;
82150         this.send();
82151     },
82152
82153     getProvider: function(){
82154         return this.provider;
82155     }
82156 });
82157
82158 /**
82159  * @class Ext.direct.RemotingProvider
82160  * @extends Ext.direct.JsonProvider
82161  * 
82162  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
82163  * server side methods on the client (a remote procedure call (RPC) type of
82164  * connection where the client can initiate a procedure on the server).</p>
82165  * 
82166  * <p>This allows for code to be organized in a fashion that is maintainable,
82167  * while providing a clear path between client and server, something that is
82168  * not always apparent when using URLs.</p>
82169  * 
82170  * <p>To accomplish this the server-side needs to describe what classes and methods
82171  * are available on the client-side. This configuration will typically be
82172  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
82173  */
82174 Ext.define('Ext.direct.RemotingProvider', {
82175     
82176     /* Begin Definitions */
82177    
82178     alias: 'direct.remotingprovider',
82179     
82180     extend: 'Ext.direct.JsonProvider', 
82181     
82182     requires: [
82183         'Ext.util.MixedCollection', 
82184         'Ext.util.DelayedTask', 
82185         'Ext.direct.Transaction',
82186         'Ext.direct.RemotingMethod'
82187     ],
82188    
82189     /* End Definitions */
82190    
82191    /**
82192      * @cfg {Object} actions
82193      * Object literal defining the server side actions and methods. For example, if
82194      * the Provider is configured with:
82195      * <pre><code>
82196 "actions":{ // each property within the 'actions' object represents a server side Class 
82197     "TestAction":[ // array of methods within each server side Class to be   
82198     {              // stubbed out on client
82199         "name":"doEcho", 
82200         "len":1            
82201     },{
82202         "name":"multiply",// name of method
82203         "len":2           // The number of parameters that will be used to create an
82204                           // array of data to send to the server side function.
82205                           // Ensure the server sends back a Number, not a String. 
82206     },{
82207         "name":"doForm",
82208         "formHandler":true, // direct the client to use specialized form handling method 
82209         "len":1
82210     }]
82211 }
82212      * </code></pre>
82213      * <p>Note that a Store is not required, a server method can be called at any time.
82214      * In the following example a <b>client side</b> handler is used to call the
82215      * server side method "multiply" in the server-side "TestAction" Class:</p>
82216      * <pre><code>
82217 TestAction.multiply(
82218     2, 4, // pass two arguments to server, so specify len=2
82219     // callback function after the server is called
82220     // result: the result returned by the server
82221     //      e: Ext.direct.RemotingEvent object
82222     function(result, e){
82223         var t = e.getTransaction();
82224         var action = t.action; // server side Class called
82225         var method = t.method; // server side method called
82226         if(e.status){
82227             var answer = Ext.encode(result); // 8
82228     
82229         }else{
82230             var msg = e.message; // failure message
82231         }
82232     }
82233 );
82234      * </code></pre>
82235      * In the example above, the server side "multiply" function will be passed two
82236      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
82237      * available as the <tt>result</tt> in the example above. 
82238      */
82239     
82240     /**
82241      * @cfg {String/Object} namespace
82242      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
82243      * Explicitly specify the namespace Object, or specify a String to have a
82244      * {@link Ext#namespace namespace created} implicitly.
82245      */
82246     
82247     /**
82248      * @cfg {String} url
82249      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
82250      */
82251     
82252     /**
82253      * @cfg {String} enableUrlEncode
82254      * Specify which param will hold the arguments for the method.
82255      * Defaults to <tt>'data'</tt>.
82256      */
82257     
82258     /**
82259      * @cfg {Number/Boolean} enableBuffer
82260      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
82261      * calls. If a number is specified this is the amount of time in milliseconds
82262      * to wait before sending a batched request.</p>
82263      * <br><p>Calls which are received within the specified timeframe will be
82264      * concatenated together and sent in a single request, optimizing the
82265      * application by reducing the amount of round trips that have to be made
82266      * to the server.</p>
82267      */
82268     enableBuffer: 10,
82269     
82270     /**
82271      * @cfg {Number} maxRetries
82272      * Number of times to re-attempt delivery on failure of a call.
82273      */
82274     maxRetries: 1,
82275     
82276     /**
82277      * @cfg {Number} timeout
82278      * The timeout to use for each request.
82279      */
82280     timeout: undefined,
82281     
82282     constructor : function(config){
82283         var me = this;
82284         me.callParent(arguments);
82285         me.addEvents(
82286             /**
82287              * @event beforecall
82288              * Fires immediately before the client-side sends off the RPC call.
82289              * By returning false from an event handler you can prevent the call from
82290              * executing.
82291              * @param {Ext.direct.RemotingProvider} provider
82292              * @param {Ext.direct.Transaction} transaction
82293              * @param {Object} meta The meta data
82294              */            
82295             'beforecall',            
82296             /**
82297              * @event call
82298              * Fires immediately after the request to the server-side is sent. This does
82299              * NOT fire after the response has come back from the call.
82300              * @param {Ext.direct.RemotingProvider} provider
82301              * @param {Ext.direct.Transaction} transaction
82302              * @param {Object} meta The meta data
82303              */            
82304             'call'
82305         );
82306         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
82307         me.transactions = Ext.create('Ext.util.MixedCollection');
82308         me.callBuffer = [];
82309     },
82310     
82311     /**
82312      * Initialize the API
82313      * @private
82314      */
82315     initAPI : function(){
82316         var actions = this.actions,
82317             namespace = this.namespace,
82318             action,
82319             cls,
82320             methods,
82321             i,
82322             len,
82323             method;
82324             
82325         for (action in actions) {
82326             cls = namespace[action];
82327             if (!cls) {
82328                 cls = namespace[action] = {};
82329             }
82330             methods = actions[action];
82331             
82332             for (i = 0, len = methods.length; i < len; ++i) {
82333                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
82334                 cls[method.name] = this.createHandler(action, method);
82335             }
82336         }
82337     },
82338     
82339     /**
82340      * Create a handler function for a direct call.
82341      * @private
82342      * @param {String} action The action the call is for
82343      * @param {Object} method The details of the method
82344      * @return {Function} A JS function that will kick off the call
82345      */
82346     createHandler : function(action, method){
82347         var me = this,
82348             handler;
82349         
82350         if (!method.formHandler) {
82351             handler = function(){
82352                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
82353             };
82354         } else {
82355             handler = function(form, callback, scope){
82356                 me.configureFormRequest(action, method, form, callback, scope);
82357             };
82358         }
82359         handler.directCfg = {
82360             action: action,
82361             method: method
82362         };
82363         return handler;
82364     },
82365     
82366     // inherit docs
82367     isConnected: function(){
82368         return !!this.connected;
82369     },
82370
82371     // inherit docs
82372     connect: function(){
82373         var me = this;
82374         
82375         if (me.url) {
82376             me.initAPI();
82377             me.connected = true;
82378             me.fireEvent('connect', me);
82379         } else if(!me.url) {
82380         }
82381     },
82382
82383     // inherit docs
82384     disconnect: function(){
82385         var me = this;
82386         
82387         if (me.connected) {
82388             me.connected = false;
82389             me.fireEvent('disconnect', me);
82390         }
82391     },
82392     
82393     /**
82394      * Run any callbacks related to the transaction.
82395      * @private
82396      * @param {Ext.direct.Transaction} transaction The transaction
82397      * @param {Ext.direct.Event} event The event
82398      */
82399     runCallback: function(transaction, event){
82400         var funcName = event.status ? 'success' : 'failure',
82401             callback,
82402             result;
82403         
82404         if (transaction && transaction.callback) {
82405             callback = transaction.callback;
82406             result = Ext.isDefined(event.result) ? event.result : event.data;
82407         
82408             if (Ext.isFunction(callback)) {
82409                 callback(result, event);
82410             } else {
82411                 Ext.callback(callback[funcName], callback.scope, [result, event]);
82412                 Ext.callback(callback.callback, callback.scope, [result, event]);
82413             }
82414         }
82415     },
82416     
82417     /**
82418      * React to the ajax request being completed
82419      * @private
82420      */
82421     onData: function(options, success, response){
82422         var me = this,
82423             i = 0,
82424             len,
82425             events,
82426             event,
82427             transaction,
82428             transactions;
82429             
82430         if (success) {
82431             events = me.createEvents(response);
82432             for (len = events.length; i < len; ++i) {
82433                 event = events[i];
82434                 transaction = me.getTransaction(event);
82435                 me.fireEvent('data', me, event);
82436                 if (transaction) {
82437                     me.runCallback(transaction, event, true);
82438                     Ext.direct.Manager.removeTransaction(transaction);
82439                 }
82440             }
82441         } else {
82442             transactions = [].concat(options.transaction);
82443             for (len = transactions.length; i < len; ++i) {
82444                 transaction = me.getTransaction(transactions[i]);
82445                 if (transaction && transaction.retryCount < me.maxRetries) {
82446                     transaction.retry();
82447                 } else {
82448                     event = Ext.create('Ext.direct.ExceptionEvent', {
82449                         data: null,
82450                         transaction: transaction,
82451                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82452                         message: 'Unable to connect to the server.',
82453                         xhr: response
82454                     });
82455                     me.fireEvent('data', me, event);
82456                     if (transaction) {
82457                         me.runCallback(transaction, event, false);
82458                         Ext.direct.Manager.removeTransaction(transaction);
82459                     }
82460                 }
82461             }
82462         }
82463     },
82464     
82465     /**
82466      * Get transaction from XHR options
82467      * @private
82468      * @param {Object} options The options sent to the Ajax request
82469      * @return {Ext.direct.Transaction} The transaction, null if not found
82470      */
82471     getTransaction: function(options){
82472         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
82473     },
82474     
82475     /**
82476      * Configure a direct request
82477      * @private
82478      * @param {String} action The action being executed
82479      * @param {Object} method The being executed
82480      */
82481     configureRequest: function(action, method, args){
82482         var me = this,
82483             callData = method.getCallData(args),
82484             data = callData.data, 
82485             callback = callData.callback, 
82486             scope = callData.scope,
82487             transaction;
82488
82489         transaction = Ext.create('Ext.direct.Transaction', {
82490             provider: me,
82491             args: args,
82492             action: action,
82493             method: method.name,
82494             data: data,
82495             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
82496         });
82497
82498         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82499             Ext.direct.Manager.addTransaction(transaction);
82500             me.queueTransaction(transaction);
82501             me.fireEvent('call', me, transaction, method);
82502         }
82503     },
82504     
82505     /**
82506      * Gets the Ajax call info for a transaction
82507      * @private
82508      * @param {Ext.direct.Transaction} transaction The transaction
82509      * @return {Object} The call params
82510      */
82511     getCallData: function(transaction){
82512         return {
82513             action: transaction.action,
82514             method: transaction.method,
82515             data: transaction.data,
82516             type: 'rpc',
82517             tid: transaction.id
82518         };
82519     },
82520     
82521     /**
82522      * Sends a request to the server
82523      * @private
82524      * @param {Object/Array} data The data to send
82525      */
82526     sendRequest : function(data){
82527         var me = this,
82528             request = {
82529                 url: me.url,
82530                 callback: me.onData,
82531                 scope: me,
82532                 transaction: data,
82533                 timeout: me.timeout
82534             }, callData,
82535             enableUrlEncode = me.enableUrlEncode,
82536             i = 0,
82537             len,
82538             params;
82539             
82540
82541         if (Ext.isArray(data)) {
82542             callData = [];
82543             for (len = data.length; i < len; ++i) {
82544                 callData.push(me.getCallData(data[i]));
82545             }
82546         } else {
82547             callData = me.getCallData(data);
82548         }
82549
82550         if (enableUrlEncode) {
82551             params = {};
82552             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
82553             request.params = params;
82554         } else {
82555             request.jsonData = callData;
82556         }
82557         Ext.Ajax.request(request);
82558     },
82559     
82560     /**
82561      * Add a new transaction to the queue
82562      * @private
82563      * @param {Ext.direct.Transaction} transaction The transaction
82564      */
82565     queueTransaction: function(transaction){
82566         var me = this,
82567             enableBuffer = me.enableBuffer;
82568         
82569         if (transaction.form) {
82570             me.sendFormRequest(transaction);
82571             return;
82572         }
82573         
82574         me.callBuffer.push(transaction);
82575         if (enableBuffer) {
82576             if (!me.callTask) {
82577                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
82578             }
82579             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
82580         } else {
82581             me.combineAndSend();
82582         }
82583     },
82584     
82585     /**
82586      * Combine any buffered requests and send them off
82587      * @private
82588      */
82589     combineAndSend : function(){
82590         var buffer = this.callBuffer,
82591             len = buffer.length;
82592             
82593         if (len > 0) {
82594             this.sendRequest(len == 1 ? buffer[0] : buffer);
82595             this.callBuffer = [];
82596         }
82597     },
82598     
82599     /**
82600      * Configure a form submission request
82601      * @private
82602      * @param {String} action The action being executed
82603      * @param {Object} method The method being executed
82604      * @param {HTMLElement} form The form being submitted
82605      * @param {Function} callback (optional) A callback to run after the form submits
82606      * @param {Object} scope (optional) A scope to execute the callback in
82607      */
82608     configureFormRequest : function(action, method, form, callback, scope){
82609         var me = this,
82610             transaction = Ext.create('Ext.direct.Transaction', {
82611                 provider: me,
82612                 action: action,
82613                 method: method.name,
82614                 args: [form, callback, scope],
82615                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
82616                 isForm: true
82617             }),
82618             isUpload,
82619             params;
82620
82621         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82622             Ext.direct.Manager.addTransaction(transaction);
82623             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
82624             
82625             params = {
82626                 extTID: transaction.id,
82627                 extAction: action,
82628                 extMethod: method.name,
82629                 extType: 'rpc',
82630                 extUpload: String(isUpload)
82631             };
82632             
82633             // change made from typeof callback check to callback.params
82634             // to support addl param passing in DirectSubmit EAC 6/2
82635             Ext.apply(transaction, {
82636                 form: Ext.getDom(form),
82637                 isUpload: isUpload,
82638                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
82639             });
82640             me.fireEvent('call', me, transaction, method);
82641             me.sendFormRequest(transaction);
82642         }
82643     },
82644     
82645     /**
82646      * Sends a form request
82647      * @private
82648      * @param {Ext.direct.Transaction} transaction The transaction to send
82649      */
82650     sendFormRequest: function(transaction){
82651         Ext.Ajax.request({
82652             url: this.url,
82653             params: transaction.params,
82654             callback: this.onData,
82655             scope: this,
82656             form: transaction.form,
82657             isUpload: transaction.isUpload,
82658             transaction: transaction
82659         });
82660     }
82661     
82662 });
82663
82664 /*
82665  * @class Ext.draw.Matrix
82666  * @private
82667  */
82668 Ext.define('Ext.draw.Matrix', {
82669
82670     /* Begin Definitions */
82671
82672     requires: ['Ext.draw.Draw'],
82673
82674     /* End Definitions */
82675
82676     constructor: function(a, b, c, d, e, f) {
82677         if (a != null) {
82678             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
82679         }
82680         else {
82681             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
82682         }
82683     },
82684
82685     add: function(a, b, c, d, e, f) {
82686         var me = this,
82687             out = [[], [], []],
82688             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82689             x,
82690             y,
82691             z,
82692             res;
82693
82694         for (x = 0; x < 3; x++) {
82695             for (y = 0; y < 3; y++) {
82696                 res = 0;
82697                 for (z = 0; z < 3; z++) {
82698                     res += me.matrix[x][z] * matrix[z][y];
82699                 }
82700                 out[x][y] = res;
82701             }
82702         }
82703         me.matrix = out;
82704     },
82705
82706     prepend: function(a, b, c, d, e, f) {
82707         var me = this,
82708             out = [[], [], []],
82709             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82710             x,
82711             y,
82712             z,
82713             res;
82714
82715         for (x = 0; x < 3; x++) {
82716             for (y = 0; y < 3; y++) {
82717                 res = 0;
82718                 for (z = 0; z < 3; z++) {
82719                     res += matrix[x][z] * me.matrix[z][y];
82720                 }
82721                 out[x][y] = res;
82722             }
82723         }
82724         me.matrix = out;
82725     },
82726
82727     invert: function() {
82728         var matrix = this.matrix,
82729             a = matrix[0][0],
82730             b = matrix[1][0],
82731             c = matrix[0][1],
82732             d = matrix[1][1],
82733             e = matrix[0][2],
82734             f = matrix[1][2],
82735             x = a * d - b * c;
82736         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
82737     },
82738
82739     clone: function() {
82740         var matrix = this.matrix,
82741             a = matrix[0][0],
82742             b = matrix[1][0],
82743             c = matrix[0][1],
82744             d = matrix[1][1],
82745             e = matrix[0][2],
82746             f = matrix[1][2];
82747         return new Ext.draw.Matrix(a, b, c, d, e, f);
82748     },
82749
82750     translate: function(x, y) {
82751         this.prepend(1, 0, 0, 1, x, y);
82752     },
82753
82754     scale: function(x, y, cx, cy) {
82755         var me = this;
82756         if (y == null) {
82757             y = x;
82758         }
82759         me.add(1, 0, 0, 1, cx, cy);
82760         me.add(x, 0, 0, y, 0, 0);
82761         me.add(1, 0, 0, 1, -cx, -cy);
82762     },
82763
82764     rotate: function(a, x, y) {
82765         a = Ext.draw.Draw.rad(a);
82766         var me = this,
82767             cos = +Math.cos(a).toFixed(9),
82768             sin = +Math.sin(a).toFixed(9);
82769         me.add(cos, sin, -sin, cos, x, y);
82770         me.add(1, 0, 0, 1, -x, -y);
82771     },
82772
82773     x: function(x, y) {
82774         var matrix = this.matrix;
82775         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
82776     },
82777
82778     y: function(x, y) {
82779         var matrix = this.matrix;
82780         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
82781     },
82782
82783     get: function(i, j) {
82784         return + this.matrix[i][j].toFixed(4);
82785     },
82786
82787     toString: function() {
82788         var me = this;
82789         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
82790     },
82791
82792     toSvg: function() {
82793         var me = this;
82794         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() + ")";
82795     },
82796
82797     toFilter: function() {
82798         var me = this;
82799         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
82800             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
82801             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
82802     },
82803
82804     offset: function() {
82805         var matrix = this.matrix;
82806         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
82807     },
82808
82809     // Split matrix into Translate Scale, Shear, and Rotate
82810     split: function () {
82811         function norm(a) {
82812             return a[0] * a[0] + a[1] * a[1];
82813         }
82814         function normalize(a) {
82815             var mag = Math.sqrt(norm(a));
82816             a[0] /= mag;
82817             a[1] /= mag;
82818         }
82819         var matrix = this.matrix,
82820             out = {
82821                 translateX: matrix[0][2],
82822                 translateY: matrix[1][2]
82823             },
82824             row;
82825
82826         // scale and shear
82827         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
82828         out.scaleX = Math.sqrt(norm(row[0]));
82829         normalize(row[0]);
82830
82831         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
82832         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
82833
82834         out.scaleY = Math.sqrt(norm(row[1]));
82835         normalize(row[1]);
82836         out.shear /= out.scaleY;
82837
82838         // rotation
82839         out.rotate = Math.asin(-row[0][1]);
82840
82841         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
82842
82843         return out;
82844     }
82845 });
82846
82847 // private - DD implementation for Panels
82848 Ext.define('Ext.draw.SpriteDD', {
82849     extend: 'Ext.dd.DragSource',
82850
82851     constructor : function(sprite, cfg){
82852         var me = this,
82853             el = sprite.el;
82854         me.sprite = sprite;
82855         me.el = el;
82856         me.dragData = {el: el, sprite: sprite};
82857         me.callParent([el, cfg]);
82858         me.sprite.setStyle('cursor', 'move');
82859     },
82860
82861     showFrame: Ext.emptyFn,
82862     createFrame : Ext.emptyFn,
82863
82864     getDragEl : function(e){
82865         return this.el;
82866     },
82867     
82868     getRegion: function() {
82869         var me = this,
82870             el = me.el,
82871             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
82872         
82873         sprite = me.sprite;
82874         bbox = sprite.getBBox();
82875         
82876         try {
82877             pos = Ext.Element.getXY(el);
82878         } catch (e) { }
82879
82880         if (!pos) {
82881             return null;
82882         }
82883
82884         x1 = pos[0];
82885         x2 = x1 + bbox.width;
82886         y1 = pos[1];
82887         y2 = y1 + bbox.height;
82888         
82889         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
82890     },
82891
82892     /*
82893       TODO(nico): Cumulative translations in VML are handled
82894       differently than in SVG. While in SVG we specify the translation
82895       relative to the original x, y position attributes, in VML the translation
82896       is a delta between the last position of the object (modified by the last
82897       translation) and the new one.
82898       
82899       In VML the translation alters the position
82900       of the object, we should change that or alter the SVG impl.
82901     */
82902      
82903     startDrag: function(x, y) {
82904         var me = this,
82905             attr = me.sprite.attr;
82906         me.prev = me.sprite.surface.transformToViewBox(x, y);
82907     },
82908
82909     onDrag: function(e) {
82910         var xy = e.getXY(),
82911             me = this,
82912             sprite = me.sprite,
82913             attr = sprite.attr, dx, dy;
82914         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
82915         dx = xy[0] - me.prev[0];
82916         dy = xy[1] - me.prev[1];
82917         sprite.setAttributes({
82918             translate: {
82919                 x: attr.translation.x + dx,
82920                 y: attr.translation.y + dy
82921             }
82922         }, true);
82923         me.prev = xy;
82924     },
82925
82926     setDragElPos: function () {
82927         // Disable automatic DOM move in DD that spoils layout of VML engine.
82928         return false;
82929     }
82930 });
82931 /**
82932  * A Sprite is an object rendered in a Drawing surface.
82933  *
82934  * # Translation
82935  *
82936  * For translate, the configuration object contains x and y attributes that indicate where to
82937  * translate the object. For example:
82938  *
82939  *     sprite.setAttributes({
82940  *       translate: {
82941  *        x: 10,
82942  *        y: 10
82943  *       }
82944  *     }, true);
82945  *
82946  *
82947  * # Rotation
82948  *
82949  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
82950  * and a `degrees` attribute that specifies the rotation in degrees. For example:
82951  *
82952  *     sprite.setAttributes({
82953  *       rotate: {
82954  *        degrees: 90
82955  *       }
82956  *     }, true);
82957  *
82958  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
82959  *
82960  *     sprite.setAttributes({
82961  *       rotate: {
82962  *        x: 0,
82963  *        y: 0,
82964  *        degrees: 90
82965  *       }
82966  *     }, true);
82967  *
82968  * will create a rotation around the `(0, 0)` axis.
82969  *
82970  *
82971  * # Scaling
82972  *
82973  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
82974  *
82975  *     sprite.setAttributes({
82976  *       scale: {
82977  *        x: 10,
82978  *        y: 3
82979  *       }
82980  *     }, true);
82981  *
82982  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
82983  *
82984  *     sprite.setAttributes({
82985  *       scale: {
82986  *        cx: 0,
82987  *        cy: 0,
82988  *        x: 10,
82989  *        y: 3
82990  *       }
82991  *     }, true);
82992  *
82993  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
82994  *
82995  *
82996  * # Creating and adding a Sprite to a Surface
82997  *
82998  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
82999  *
83000  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
83001  *
83002  *     var sprite = Ext.create('Ext.draw.Sprite', {
83003  *         type: 'circle',
83004  *         fill: '#ff0',
83005  *         surface: drawComponent.surface,
83006  *         radius: 5
83007  *     });
83008  *
83009  * Sprites can also be added to the surface as a configuration object:
83010  *
83011  *     var sprite = drawComponent.surface.add({
83012  *         type: 'circle',
83013  *         fill: '#ff0',
83014  *         radius: 5
83015  *     });
83016  *
83017  * In order to properly apply properties and render the sprite we have to
83018  * `show` the sprite setting the option `redraw` to `true`:
83019  *
83020  *     sprite.show(true);
83021  *
83022  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
83023  * add method to append a new sprite to the canvas. For example:
83024  *
83025  *     drawComponent.surface.add({
83026  *         type: 'circle',
83027  *         fill: '#ffc',
83028  *         radius: 100,
83029  *         x: 100,
83030  *         y: 100
83031  *     });
83032  */
83033 Ext.define('Ext.draw.Sprite', {
83034
83035     /* Begin Definitions */
83036
83037     mixins: {
83038         observable: 'Ext.util.Observable',
83039         animate: 'Ext.util.Animate'
83040     },
83041
83042     requires: ['Ext.draw.SpriteDD'],
83043
83044     /* End Definitions */
83045
83046     /**
83047      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
83048      */
83049
83050     /**
83051      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
83052      */
83053
83054     /**
83055      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
83056      */
83057
83058     /**
83059      * @cfg {Number} size Used in square sprites, the dimension of the square
83060      */
83061
83062     /**
83063      * @cfg {Number} radius Used in circle sprites, the radius of the circle
83064      */
83065
83066     /**
83067      * @cfg {Number} x The position along the x-axis
83068      */
83069
83070     /**
83071      * @cfg {Number} y The position along the y-axis
83072      */
83073
83074     /**
83075      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
83076      */
83077
83078     /**
83079      * @cfg {Number} opacity The opacity of the sprite
83080      */
83081
83082     /**
83083      * @cfg {String} fill The fill color
83084      */
83085
83086     /**
83087      * @cfg {String} stroke The stroke color
83088      */
83089
83090     /**
83091      * @cfg {Number} stroke-width The width of the stroke
83092      */
83093
83094     /**
83095      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
83096      */
83097
83098     /**
83099      * @cfg {String} text Used with text type sprites. The text itself
83100      */
83101
83102     /**
83103      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
83104      * {@link Ext.draw.Surface}
83105      */
83106
83107     /**
83108      * @cfg {Boolean} draggable True to make the sprite draggable.
83109      */
83110
83111     dirty: false,
83112     dirtyHidden: false,
83113     dirtyTransform: false,
83114     dirtyPath: true,
83115     dirtyFont: true,
83116     zIndexDirty: true,
83117     isSprite: true,
83118     zIndex: 0,
83119     fontProperties: [
83120         'font',
83121         'font-size',
83122         'font-weight',
83123         'font-style',
83124         'font-family',
83125         'text-anchor',
83126         'text'
83127     ],
83128     pathProperties: [
83129         'x',
83130         'y',
83131         'd',
83132         'path',
83133         'height',
83134         'width',
83135         'radius',
83136         'r',
83137         'rx',
83138         'ry',
83139         'cx',
83140         'cy'
83141     ],
83142     constructor: function(config) {
83143         var me = this;
83144         config = config || {};
83145         me.id = Ext.id(null, 'ext-sprite-');
83146         me.transformations = [];
83147         Ext.copyTo(this, config, 'surface,group,type,draggable');
83148         //attribute bucket
83149         me.bbox = {};
83150         me.attr = {
83151             zIndex: 0,
83152             translation: {
83153                 x: null,
83154                 y: null
83155             },
83156             rotation: {
83157                 degrees: null,
83158                 x: null,
83159                 y: null
83160             },
83161             scaling: {
83162                 x: null,
83163                 y: null,
83164                 cx: null,
83165                 cy: null
83166             }
83167         };
83168         //delete not bucket attributes
83169         delete config.surface;
83170         delete config.group;
83171         delete config.type;
83172         delete config.draggable;
83173         me.setAttributes(config);
83174         me.addEvents(
83175             'beforedestroy',
83176             'destroy',
83177             'render',
83178             'mousedown',
83179             'mouseup',
83180             'mouseover',
83181             'mouseout',
83182             'mousemove',
83183             'click'
83184         );
83185         me.mixins.observable.constructor.apply(this, arguments);
83186     },
83187
83188     /**
83189      * @property {Ext.dd.DragSource} dd
83190      * If this Sprite is configured {@link #draggable}, this property will contain
83191      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
83192      *
83193      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
83194      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
83195      */
83196
83197     initDraggable: function() {
83198         var me = this;
83199         me.draggable = true;
83200         //create element if it doesn't exist.
83201         if (!me.el) {
83202             me.surface.createSpriteElement(me);
83203         }
83204         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
83205         me.on('beforedestroy', me.dd.destroy, me.dd);
83206     },
83207
83208     /**
83209      * Change the attributes of the sprite.
83210      * @param {Object} attrs attributes to be changed on the sprite.
83211      * @param {Boolean} redraw Flag to immediatly draw the change.
83212      * @return {Ext.draw.Sprite} this
83213      */
83214     setAttributes: function(attrs, redraw) {
83215         var me = this,
83216             fontProps = me.fontProperties,
83217             fontPropsLength = fontProps.length,
83218             pathProps = me.pathProperties,
83219             pathPropsLength = pathProps.length,
83220             hasSurface = !!me.surface,
83221             custom = hasSurface && me.surface.customAttributes || {},
83222             spriteAttrs = me.attr,
83223             attr, i, translate, translation, rotate, rotation, scale, scaling;
83224
83225         attrs = Ext.apply({}, attrs);
83226         for (attr in custom) {
83227             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
83228                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
83229             }
83230         }
83231
83232         // Flag a change in hidden
83233         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
83234             me.dirtyHidden = true;
83235         }
83236
83237         // Flag path change
83238         for (i = 0; i < pathPropsLength; i++) {
83239             attr = pathProps[i];
83240             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83241                 me.dirtyPath = true;
83242                 break;
83243             }
83244         }
83245
83246         // Flag zIndex change
83247         if ('zIndex' in attrs) {
83248             me.zIndexDirty = true;
83249         }
83250
83251         // Flag font/text change
83252         for (i = 0; i < fontPropsLength; i++) {
83253             attr = fontProps[i];
83254             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83255                 me.dirtyFont = true;
83256                 break;
83257             }
83258         }
83259
83260         translate = attrs.translate;
83261         translation = spriteAttrs.translation;
83262         if (translate) {
83263             if ((translate.x && translate.x !== translation.x) ||
83264                 (translate.y && translate.y !== translation.y)) {
83265                 Ext.apply(translation, translate);
83266                 me.dirtyTransform = true;
83267             }
83268             delete attrs.translate;
83269         }
83270
83271         rotate = attrs.rotate;
83272         rotation = spriteAttrs.rotation;
83273         if (rotate) {
83274             if ((rotate.x && rotate.x !== rotation.x) ||
83275                 (rotate.y && rotate.y !== rotation.y) ||
83276                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
83277                 Ext.apply(rotation, rotate);
83278                 me.dirtyTransform = true;
83279             }
83280             delete attrs.rotate;
83281         }
83282
83283         scale = attrs.scale;
83284         scaling = spriteAttrs.scaling;
83285         if (scale) {
83286             if ((scale.x && scale.x !== scaling.x) ||
83287                 (scale.y && scale.y !== scaling.y) ||
83288                 (scale.cx && scale.cx !== scaling.cx) ||
83289                 (scale.cy && scale.cy !== scaling.cy)) {
83290                 Ext.apply(scaling, scale);
83291                 me.dirtyTransform = true;
83292             }
83293             delete attrs.scale;
83294         }
83295
83296         Ext.apply(spriteAttrs, attrs);
83297         me.dirty = true;
83298
83299         if (redraw === true && hasSurface) {
83300             me.redraw();
83301         }
83302         return this;
83303     },
83304
83305     /**
83306      * Retrieves the bounding box of the sprite.
83307      * This will be returned as an object with x, y, width, and height properties.
83308      * @return {Object} bbox
83309      */
83310     getBBox: function() {
83311         return this.surface.getBBox(this);
83312     },
83313
83314     setText: function(text) {
83315         return this.surface.setText(this, text);
83316     },
83317
83318     /**
83319      * Hides the sprite.
83320      * @param {Boolean} redraw Flag to immediatly draw the change.
83321      * @return {Ext.draw.Sprite} this
83322      */
83323     hide: function(redraw) {
83324         this.setAttributes({
83325             hidden: true
83326         }, redraw);
83327         return this;
83328     },
83329
83330     /**
83331      * Shows the sprite.
83332      * @param {Boolean} redraw Flag to immediatly draw the change.
83333      * @return {Ext.draw.Sprite} this
83334      */
83335     show: function(redraw) {
83336         this.setAttributes({
83337             hidden: false
83338         }, redraw);
83339         return this;
83340     },
83341
83342     /**
83343      * Removes the sprite.
83344      */
83345     remove: function() {
83346         if (this.surface) {
83347             this.surface.remove(this);
83348             return true;
83349         }
83350         return false;
83351     },
83352
83353     onRemove: function() {
83354         this.surface.onRemove(this);
83355     },
83356
83357     /**
83358      * Removes the sprite and clears all listeners.
83359      */
83360     destroy: function() {
83361         var me = this;
83362         if (me.fireEvent('beforedestroy', me) !== false) {
83363             me.remove();
83364             me.surface.onDestroy(me);
83365             me.clearListeners();
83366             me.fireEvent('destroy');
83367         }
83368     },
83369
83370     /**
83371      * Redraws the sprite.
83372      * @return {Ext.draw.Sprite} this
83373      */
83374     redraw: function() {
83375         this.surface.renderItem(this);
83376         return this;
83377     },
83378
83379     /**
83380      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
83381      * @param {String/Object} property The style property to be set, or an object of multiple styles.
83382      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
83383      * @return {Ext.draw.Sprite} this
83384      */
83385     setStyle: function() {
83386         this.el.setStyle.apply(this.el, arguments);
83387         return this;
83388     },
83389
83390     /**
83391      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
83392      * is severly limited in VML.
83393      * @param {String/String[]} className The CSS class to add, or an array of classes
83394      * @return {Ext.draw.Sprite} this
83395      */
83396     addCls: function(obj) {
83397         this.surface.addCls(this, obj);
83398         return this;
83399     },
83400
83401     /**
83402      * Removes one or more CSS classes from the element.
83403      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
83404      * is severly limited in VML.
83405      * @return {Ext.draw.Sprite} this
83406      */
83407     removeCls: function(obj) {
83408         this.surface.removeCls(this, obj);
83409         return this;
83410     }
83411 });
83412
83413 /**
83414  * @class Ext.draw.engine.Svg
83415  * @extends Ext.draw.Surface
83416  * Provides specific methods to draw with SVG.
83417  */
83418 Ext.define('Ext.draw.engine.Svg', {
83419
83420     /* Begin Definitions */
83421
83422     extend: 'Ext.draw.Surface',
83423
83424     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
83425
83426     /* End Definitions */
83427
83428     engine: 'Svg',
83429
83430     trimRe: /^\s+|\s+$/g,
83431     spacesRe: /\s+/,
83432     xlink: "http:/" + "/www.w3.org/1999/xlink",
83433
83434     translateAttrs: {
83435         radius: "r",
83436         radiusX: "rx",
83437         radiusY: "ry",
83438         path: "d",
83439         lineWidth: "stroke-width",
83440         fillOpacity: "fill-opacity",
83441         strokeOpacity: "stroke-opacity",
83442         strokeLinejoin: "stroke-linejoin"
83443     },
83444     
83445     parsers: {},
83446
83447     minDefaults: {
83448         circle: {
83449             cx: 0,
83450             cy: 0,
83451             r: 0,
83452             fill: "none",
83453             stroke: null,
83454             "stroke-width": null,
83455             opacity: null,
83456             "fill-opacity": null,
83457             "stroke-opacity": null
83458         },
83459         ellipse: {
83460             cx: 0,
83461             cy: 0,
83462             rx: 0,
83463             ry: 0,
83464             fill: "none",
83465             stroke: null,
83466             "stroke-width": null,
83467             opacity: null,
83468             "fill-opacity": null,
83469             "stroke-opacity": null
83470         },
83471         rect: {
83472             x: 0,
83473             y: 0,
83474             width: 0,
83475             height: 0,
83476             rx: 0,
83477             ry: 0,
83478             fill: "none",
83479             stroke: null,
83480             "stroke-width": null,
83481             opacity: null,
83482             "fill-opacity": null,
83483             "stroke-opacity": null
83484         },
83485         text: {
83486             x: 0,
83487             y: 0,
83488             "text-anchor": "start",
83489             "font-family": null,
83490             "font-size": null,
83491             "font-weight": null,
83492             "font-style": null,
83493             fill: "#000",
83494             stroke: null,
83495             "stroke-width": null,
83496             opacity: null,
83497             "fill-opacity": null,
83498             "stroke-opacity": null
83499         },
83500         path: {
83501             d: "M0,0",
83502             fill: "none",
83503             stroke: null,
83504             "stroke-width": null,
83505             opacity: null,
83506             "fill-opacity": null,
83507             "stroke-opacity": null
83508         },
83509         image: {
83510             x: 0,
83511             y: 0,
83512             width: 0,
83513             height: 0,
83514             preserveAspectRatio: "none",
83515             opacity: null
83516         }
83517     },
83518
83519     createSvgElement: function(type, attrs) {
83520         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
83521             key;
83522         if (attrs) {
83523             for (key in attrs) {
83524                 el.setAttribute(key, String(attrs[key]));
83525             }
83526         }
83527         return el;
83528     },
83529
83530     createSpriteElement: function(sprite) {
83531         // Create svg element and append to the DOM.
83532         var el = this.createSvgElement(sprite.type);
83533         el.id = sprite.id;
83534         if (el.style) {
83535             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
83536         }
83537         sprite.el = Ext.get(el);
83538         this.applyZIndex(sprite); //performs the insertion
83539         sprite.matrix = Ext.create('Ext.draw.Matrix');
83540         sprite.bbox = {
83541             plain: 0,
83542             transform: 0
83543         };
83544         sprite.fireEvent("render", sprite);
83545         return el;
83546     },
83547
83548     getBBox: function (sprite, isWithoutTransform) {
83549         var realPath = this["getPath" + sprite.type](sprite);
83550         if (isWithoutTransform) {
83551             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
83552             return sprite.bbox.plain;
83553         }
83554         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
83555         return sprite.bbox.transform;
83556     },
83557     
83558     getBBoxText: function (sprite) {
83559         var bbox = {},
83560             bb, height, width, i, ln, el;
83561
83562         if (sprite && sprite.el) {
83563             el = sprite.el.dom;
83564             try {
83565                 bbox = el.getBBox();
83566                 return bbox;
83567             } catch(e) {
83568                 // Firefox 3.0.x plays badly here
83569             }
83570             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
83571             ln = el.getNumberOfChars();
83572             for (i = 0; i < ln; i++) {
83573                 bb = el.getExtentOfChar(i);
83574                 bbox.y = Math.min(bb.y, bbox.y);
83575                 height = bb.y + bb.height - bbox.y;
83576                 bbox.height = Math.max(bbox.height, height);
83577                 width = bb.x + bb.width - bbox.x;
83578                 bbox.width = Math.max(bbox.width, width);
83579             }
83580             return bbox;
83581         }
83582     },
83583
83584     hide: function() {
83585         Ext.get(this.el).hide();
83586     },
83587
83588     show: function() {
83589         Ext.get(this.el).show();
83590     },
83591
83592     hidePrim: function(sprite) {
83593         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83594     },
83595
83596     showPrim: function(sprite) {
83597         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83598     },
83599
83600     getDefs: function() {
83601         return this._defs || (this._defs = this.createSvgElement("defs"));
83602     },
83603
83604     transform: function(sprite) {
83605         var me = this,
83606             matrix = Ext.create('Ext.draw.Matrix'),
83607             transforms = sprite.transformations,
83608             transformsLength = transforms.length,
83609             i = 0,
83610             transform, type;
83611             
83612         for (; i < transformsLength; i++) {
83613             transform = transforms[i];
83614             type = transform.type;
83615             if (type == "translate") {
83616                 matrix.translate(transform.x, transform.y);
83617             }
83618             else if (type == "rotate") {
83619                 matrix.rotate(transform.degrees, transform.x, transform.y);
83620             }
83621             else if (type == "scale") {
83622                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
83623             }
83624         }
83625         sprite.matrix = matrix;
83626         sprite.el.set({transform: matrix.toSvg()});
83627     },
83628
83629     setSize: function(w, h) {
83630         var me = this,
83631             el = me.el;
83632         
83633         w = +w || me.width;
83634         h = +h || me.height;
83635         me.width = w;
83636         me.height = h;
83637
83638         el.setSize(w, h);
83639         el.set({
83640             width: w,
83641             height: h
83642         });
83643         me.callParent([w, h]);
83644     },
83645
83646     /**
83647      * Get the region for the surface's canvas area
83648      * @returns {Ext.util.Region}
83649      */
83650     getRegion: function() {
83651         // Mozilla requires using the background rect because the svg element returns an
83652         // incorrect region. Webkit gives no region for the rect and must use the svg element.
83653         var svgXY = this.el.getXY(),
83654             rectXY = this.bgRect.getXY(),
83655             max = Math.max,
83656             x = max(svgXY[0], rectXY[0]),
83657             y = max(svgXY[1], rectXY[1]);
83658         return {
83659             left: x,
83660             top: y,
83661             right: x + this.width,
83662             bottom: y + this.height
83663         };
83664     },
83665
83666     onRemove: function(sprite) {
83667         if (sprite.el) {
83668             sprite.el.remove();
83669             delete sprite.el;
83670         }
83671         this.callParent(arguments);
83672     },
83673     
83674     setViewBox: function(x, y, width, height) {
83675         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
83676             this.callParent(arguments);
83677             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
83678         }
83679     },
83680
83681     render: function (container) {
83682         var me = this;
83683         if (!me.el) {
83684             var width = me.width || 10,
83685                 height = me.height || 10,
83686                 el = me.createSvgElement('svg', {
83687                     xmlns: "http:/" + "/www.w3.org/2000/svg",
83688                     version: 1.1,
83689                     width: width,
83690                     height: height
83691                 }),
83692                 defs = me.getDefs(),
83693
83694                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
83695                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
83696                 // use it rather than the svg element for retrieving the correct client rect of the
83697                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
83698                 bgRect = me.createSvgElement("rect", {
83699                     width: "100%",
83700                     height: "100%",
83701                     fill: "#000",
83702                     stroke: "none",
83703                     opacity: 0
83704                 }),
83705                 webkitRect;
83706             
83707                 if (Ext.isSafari3) {
83708                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
83709                     webkitRect = me.createSvgElement("rect", {
83710                         x: -10,
83711                         y: -10,
83712                         width: "110%",
83713                         height: "110%",
83714                         fill: "none",
83715                         stroke: "#000"
83716                     });
83717                 }
83718             el.appendChild(defs);
83719             if (Ext.isSafari3) {
83720                 el.appendChild(webkitRect);
83721             }
83722             el.appendChild(bgRect);
83723             container.appendChild(el);
83724             me.el = Ext.get(el);
83725             me.bgRect = Ext.get(bgRect);
83726             if (Ext.isSafari3) {
83727                 me.webkitRect = Ext.get(webkitRect);
83728                 me.webkitRect.hide();
83729             }
83730             me.el.on({
83731                 scope: me,
83732                 mouseup: me.onMouseUp,
83733                 mousedown: me.onMouseDown,
83734                 mouseover: me.onMouseOver,
83735                 mouseout: me.onMouseOut,
83736                 mousemove: me.onMouseMove,
83737                 mouseenter: me.onMouseEnter,
83738                 mouseleave: me.onMouseLeave,
83739                 click: me.onClick
83740             });
83741         }
83742         me.renderAll();
83743     },
83744
83745     // private
83746     onMouseEnter: function(e) {
83747         if (this.el.parent().getRegion().contains(e.getPoint())) {
83748             this.fireEvent('mouseenter', e);
83749         }
83750     },
83751
83752     // private
83753     onMouseLeave: function(e) {
83754         if (!this.el.parent().getRegion().contains(e.getPoint())) {
83755             this.fireEvent('mouseleave', e);
83756         }
83757     },
83758     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
83759     processEvent: function(name, e) {
83760         var target = e.getTarget(),
83761             surface = this.surface,
83762             sprite;
83763
83764         this.fireEvent(name, e);
83765         // We wrap text types in a tspan, sprite is the parent.
83766         if (target.nodeName == "tspan" && target.parentNode) {
83767             target = target.parentNode;
83768         }
83769         sprite = this.items.get(target.id);
83770         if (sprite) {
83771             sprite.fireEvent(name, sprite, e);
83772         }
83773     },
83774
83775     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
83776      * the baseline for text the vertical middle of the text to be the same as VML.
83777      */
83778     tuneText: function (sprite, attrs) {
83779         var el = sprite.el.dom,
83780             tspans = [],
83781             height, tspan, text, i, ln, texts, factor;
83782
83783         if (attrs.hasOwnProperty("text")) {
83784            tspans = this.setText(sprite, attrs.text);
83785         }
83786         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
83787         if (tspans.length) {
83788             height = this.getBBoxText(sprite).height;
83789             for (i = 0, ln = tspans.length; i < ln; i++) {
83790                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
83791                 // so we are going to normalize that here
83792                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
83793                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
83794             }
83795             sprite.dirty = true;
83796         }
83797     },
83798
83799     setText: function(sprite, textString) {
83800          var me = this,
83801              el = sprite.el.dom,
83802              x = el.getAttribute("x"),
83803              tspans = [],
83804              height, tspan, text, i, ln, texts;
83805         
83806         while (el.firstChild) {
83807             el.removeChild(el.firstChild);
83808         }
83809         // Wrap each row into tspan to emulate rows
83810         texts = String(textString).split("\n");
83811         for (i = 0, ln = texts.length; i < ln; i++) {
83812             text = texts[i];
83813             if (text) {
83814                 tspan = me.createSvgElement("tspan");
83815                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
83816                 tspan.setAttribute("x", x);
83817                 el.appendChild(tspan);
83818                 tspans[i] = tspan;
83819             }
83820         }
83821         return tspans;
83822     },
83823
83824     renderAll: function() {
83825         this.items.each(this.renderItem, this);
83826     },
83827
83828     renderItem: function (sprite) {
83829         if (!this.el) {
83830             return;
83831         }
83832         if (!sprite.el) {
83833             this.createSpriteElement(sprite);
83834         }
83835         if (sprite.zIndexDirty) {
83836             this.applyZIndex(sprite);
83837         }
83838         if (sprite.dirty) {
83839             this.applyAttrs(sprite);
83840             this.applyTransformations(sprite);
83841         }
83842     },
83843
83844     redraw: function(sprite) {
83845         sprite.dirty = sprite.zIndexDirty = true;
83846         this.renderItem(sprite);
83847     },
83848
83849     applyAttrs: function (sprite) {
83850         var me = this,
83851             el = sprite.el,
83852             group = sprite.group,
83853             sattr = sprite.attr,
83854             parsers = me.parsers,
83855             //Safari does not handle linear gradients correctly in quirksmode
83856             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83857             //ref: EXTJSIV-1472
83858             gradientsMap = me.gradientsMap || {},
83859             safariFix = Ext.isSafari && !Ext.isStrict,
83860             groups, i, ln, attrs, font, key, style, name, rect;
83861
83862         if (group) {
83863             groups = [].concat(group);
83864             ln = groups.length;
83865             for (i = 0; i < ln; i++) {
83866                 group = groups[i];
83867                 me.getGroup(group).add(sprite);
83868             }
83869             delete sprite.group;
83870         }
83871         attrs = me.scrubAttrs(sprite) || {};
83872
83873         // if (sprite.dirtyPath) {
83874             sprite.bbox.plain = 0;
83875             sprite.bbox.transform = 0;
83876             if (sprite.type == "circle" || sprite.type == "ellipse") {
83877                 attrs.cx = attrs.cx || attrs.x;
83878                 attrs.cy = attrs.cy || attrs.y;
83879             }
83880             else if (sprite.type == "rect") {
83881                 attrs.rx = attrs.ry = attrs.r;
83882             }
83883             else if (sprite.type == "path" && attrs.d) {
83884                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
83885             }
83886             sprite.dirtyPath = false;
83887         // }
83888         // else {
83889         //     delete attrs.d;
83890         // }
83891
83892         if (attrs['clip-rect']) {
83893             me.setClip(sprite, attrs);
83894             delete attrs['clip-rect'];
83895         }
83896         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
83897             el.set({ style: "font: " + attrs.font});
83898             sprite.dirtyFont = false;
83899         }
83900         if (sprite.type == "image") {
83901             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
83902         }
83903         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
83904
83905         if (sprite.dirtyHidden) {
83906             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
83907             sprite.dirtyHidden = false;
83908         }
83909         for (key in attrs) {
83910             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
83911                 //Safari does not handle linear gradients correctly in quirksmode
83912                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83913                 //ref: EXTJSIV-1472
83914                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
83915                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
83916                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
83917                     attrs[key] = gradientsMap[attrs[key]];
83918                 }
83919                 if (key in parsers) {
83920                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
83921                 } else {
83922                     el.dom.setAttribute(key, attrs[key]);
83923                 }
83924             }
83925         }
83926         
83927         if (sprite.type == 'text') {
83928             me.tuneText(sprite, attrs);
83929         }
83930
83931         //set styles
83932         style = sattr.style;
83933         if (style) {
83934             el.setStyle(style);
83935         }
83936
83937         sprite.dirty = false;
83938
83939         if (Ext.isSafari3) {
83940             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
83941             me.webkitRect.show();
83942             setTimeout(function () {
83943                 me.webkitRect.hide();
83944             });
83945         }
83946     },
83947
83948     setClip: function(sprite, params) {
83949         var me = this,
83950             rect = params["clip-rect"],
83951             clipEl, clipPath;
83952         if (rect) {
83953             if (sprite.clip) {
83954                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
83955             }
83956             clipEl = me.createSvgElement('clipPath');
83957             clipPath = me.createSvgElement('rect');
83958             clipEl.id = Ext.id(null, 'ext-clip-');
83959             clipPath.setAttribute("x", rect.x);
83960             clipPath.setAttribute("y", rect.y);
83961             clipPath.setAttribute("width", rect.width);
83962             clipPath.setAttribute("height", rect.height);
83963             clipEl.appendChild(clipPath);
83964             me.getDefs().appendChild(clipEl);
83965             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
83966             sprite.clip = clipPath;
83967         }
83968         // if (!attrs[key]) {
83969         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
83970         //     clip && clip.parentNode.removeChild(clip);
83971         //     sprite.el.setAttribute("clip-path", "");
83972         //     delete attrss.clip;
83973         // }
83974     },
83975
83976     /**
83977      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
83978      * @param {Ext.draw.Sprite} sprite
83979      */
83980     applyZIndex: function(sprite) {
83981         var me = this,
83982             items = me.items,
83983             idx = items.indexOf(sprite),
83984             el = sprite.el,
83985             prevEl;
83986         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
83987             if (idx > 0) {
83988                 // Find the first previous sprite which has its DOM element created already
83989                 do {
83990                     prevEl = items.getAt(--idx).el;
83991                 } while (!prevEl && idx > 0);
83992             }
83993             el.insertAfter(prevEl || me.bgRect);
83994         }
83995         sprite.zIndexDirty = false;
83996     },
83997
83998     createItem: function (config) {
83999         var sprite = Ext.create('Ext.draw.Sprite', config);
84000         sprite.surface = this;
84001         return sprite;
84002     },
84003
84004     addGradient: function(gradient) {
84005         gradient = Ext.draw.Draw.parseGradient(gradient);
84006         var me = this,
84007             ln = gradient.stops.length,
84008             vector = gradient.vector,
84009             //Safari does not handle linear gradients correctly in quirksmode
84010             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84011             //ref: EXTJSIV-1472
84012             usePlain = Ext.isSafari && !Ext.isStrict,
84013             gradientEl, stop, stopEl, i, gradientsMap;
84014             
84015         gradientsMap = me.gradientsMap || {};
84016         
84017         if (!usePlain) {
84018             if (gradient.type == "linear") {
84019                 gradientEl = me.createSvgElement("linearGradient");
84020                 gradientEl.setAttribute("x1", vector[0]);
84021                 gradientEl.setAttribute("y1", vector[1]);
84022                 gradientEl.setAttribute("x2", vector[2]);
84023                 gradientEl.setAttribute("y2", vector[3]);
84024             }
84025             else {
84026                 gradientEl = me.createSvgElement("radialGradient");
84027                 gradientEl.setAttribute("cx", gradient.centerX);
84028                 gradientEl.setAttribute("cy", gradient.centerY);
84029                 gradientEl.setAttribute("r", gradient.radius);
84030                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
84031                     gradientEl.setAttribute("fx", gradient.focalX);
84032                     gradientEl.setAttribute("fy", gradient.focalY);
84033                 }
84034             }
84035             gradientEl.id = gradient.id;
84036             me.getDefs().appendChild(gradientEl);
84037             for (i = 0; i < ln; i++) {
84038                 stop = gradient.stops[i];
84039                 stopEl = me.createSvgElement("stop");
84040                 stopEl.setAttribute("offset", stop.offset + "%");
84041                 stopEl.setAttribute("stop-color", stop.color);
84042                 stopEl.setAttribute("stop-opacity",stop.opacity);
84043                 gradientEl.appendChild(stopEl);
84044             }
84045         } else {
84046             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
84047         }
84048         me.gradientsMap = gradientsMap;
84049     },
84050
84051     /**
84052      * Checks if the specified CSS class exists on this element's DOM node.
84053      * @param {String} className The CSS class to check for
84054      * @return {Boolean} True if the class exists, else false
84055      */
84056     hasCls: function(sprite, className) {
84057         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
84058     },
84059
84060     addCls: function(sprite, className) {
84061         var el = sprite.el,
84062             i,
84063             len,
84064             v,
84065             cls = [],
84066             curCls =  el.getAttribute('class') || '';
84067         // Separate case is for speed
84068         if (!Ext.isArray(className)) {
84069             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
84070                 el.set({ 'class': curCls + ' ' + className });
84071             }
84072         }
84073         else {
84074             for (i = 0, len = className.length; i < len; i++) {
84075                 v = className[i];
84076                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
84077                     cls.push(v);
84078                 }
84079             }
84080             if (cls.length) {
84081                 el.set({ 'class': ' ' + cls.join(' ') });
84082             }
84083         }
84084     },
84085
84086     removeCls: function(sprite, className) {
84087         var me = this,
84088             el = sprite.el,
84089             curCls =  el.getAttribute('class') || '',
84090             i, idx, len, cls, elClasses;
84091         if (!Ext.isArray(className)){
84092             className = [className];
84093         }
84094         if (curCls) {
84095             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
84096             for (i = 0, len = className.length; i < len; i++) {
84097                 cls = className[i];
84098                 if (typeof cls == 'string') {
84099                     cls = cls.replace(me.trimRe, '');
84100                     idx = Ext.Array.indexOf(elClasses, cls);
84101                     if (idx != -1) {
84102                         Ext.Array.erase(elClasses, idx, 1);
84103                     }
84104                 }
84105             }
84106             el.set({ 'class': elClasses.join(' ') });
84107         }
84108     },
84109
84110     destroy: function() {
84111         var me = this;
84112         
84113         me.callParent();
84114         if (me.el) {
84115             me.el.remove();
84116         }
84117         delete me.el;
84118     }
84119 });
84120 /**
84121  * @class Ext.draw.engine.Vml
84122  * @extends Ext.draw.Surface
84123  * Provides specific methods to draw with VML.
84124  */
84125
84126 Ext.define('Ext.draw.engine.Vml', {
84127
84128     /* Begin Definitions */
84129
84130     extend: 'Ext.draw.Surface',
84131
84132     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84133
84134     /* End Definitions */
84135
84136     engine: 'Vml',
84137
84138     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
84139     bitesRe: /([clmz]),?([^clmz]*)/gi,
84140     valRe: /-?[^,\s-]+/g,
84141     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
84142     pathlike: /^(path|rect)$/,
84143     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
84144     partialPathRe: /[clmz]/g,
84145     fontFamilyRe: /^['"]+|['"]+$/g,
84146     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
84147     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
84148     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
84149     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
84150     zoom: 21600,
84151     coordsize: 1000,
84152     coordorigin: '0 0',
84153
84154     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
84155     orderSpritesByZIndex: false,
84156
84157     // @private
84158     // Convert an SVG standard path into a VML path
84159     path2vml: function (path) {
84160         var me = this,
84161             nonVML =  me.NonVmlPathRe,
84162             map = me.map,
84163             val = me.valRe,
84164             zoom = me.zoom,
84165             bites = me.bitesRe,
84166             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
84167             res, pa, p, r, i, ii, j, jj;
84168         if (String(path).match(nonVML)) {
84169             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
84170         } else if (!String(path).match(me.partialPathRe)) {
84171             res = String(path).replace(bites, function (all, command, args) {
84172                 var vals = [],
84173                     isMove = command.toLowerCase() == "m",
84174                     res = map[command];
84175                 args.replace(val, function (value) {
84176                     if (isMove && vals[length] == 2) {
84177                         res += vals + map[command == "m" ? "l" : "L"];
84178                         vals = [];
84179                     }
84180                     vals.push(Math.round(value * zoom));
84181                 });
84182                 return res + vals;
84183             });
84184             return res;
84185         }
84186         pa = command(path);
84187         res = [];
84188         for (i = 0, ii = pa.length; i < ii; i++) {
84189             p = pa[i];
84190             r = pa[i][0].toLowerCase();
84191             if (r == "z") {
84192                 r = "x";
84193             }
84194             for (j = 1, jj = p.length; j < jj; j++) {
84195                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
84196             }
84197             res.push(r);
84198         }
84199         return res.join(" ");
84200     },
84201
84202     // @private - set of attributes which need to be translated from the sprite API to the native browser API
84203     translateAttrs: {
84204         radius: "r",
84205         radiusX: "rx",
84206         radiusY: "ry",
84207         lineWidth: "stroke-width",
84208         fillOpacity: "fill-opacity",
84209         strokeOpacity: "stroke-opacity",
84210         strokeLinejoin: "stroke-linejoin"
84211     },
84212
84213     // @private - Minimun set of defaults for different types of sprites.
84214     minDefaults: {
84215         circle: {
84216             fill: "none",
84217             stroke: null,
84218             "stroke-width": null,
84219             opacity: null,
84220             "fill-opacity": null,
84221             "stroke-opacity": null
84222         },
84223         ellipse: {
84224             cx: 0,
84225             cy: 0,
84226             rx: 0,
84227             ry: 0,
84228             fill: "none",
84229             stroke: null,
84230             "stroke-width": null,
84231             opacity: null,
84232             "fill-opacity": null,
84233             "stroke-opacity": null
84234         },
84235         rect: {
84236             x: 0,
84237             y: 0,
84238             width: 0,
84239             height: 0,
84240             rx: 0,
84241             ry: 0,
84242             fill: "none",
84243             stroke: null,
84244             "stroke-width": null,
84245             opacity: null,
84246             "fill-opacity": null,
84247             "stroke-opacity": null
84248         },
84249         text: {
84250             x: 0,
84251             y: 0,
84252             "text-anchor": "start",
84253             font: '10px "Arial"',
84254             fill: "#000",
84255             stroke: null,
84256             "stroke-width": null,
84257             opacity: null,
84258             "fill-opacity": null,
84259             "stroke-opacity": null
84260         },
84261         path: {
84262             d: "M0,0",
84263             fill: "none",
84264             stroke: null,
84265             "stroke-width": null,
84266             opacity: null,
84267             "fill-opacity": null,
84268             "stroke-opacity": null
84269         },
84270         image: {
84271             x: 0,
84272             y: 0,
84273             width: 0,
84274             height: 0,
84275             preserveAspectRatio: "none",
84276             opacity: null
84277         }
84278     },
84279
84280     // private
84281     onMouseEnter: function(e) {
84282         this.fireEvent("mouseenter", e);
84283     },
84284
84285     // private
84286     onMouseLeave: function(e) {
84287         this.fireEvent("mouseleave", e);
84288     },
84289
84290     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84291     processEvent: function(name, e) {
84292         var target = e.getTarget(),
84293             surface = this.surface,
84294             sprite;
84295         this.fireEvent(name, e);
84296         sprite = this.items.get(target.id);
84297         if (sprite) {
84298             sprite.fireEvent(name, sprite, e);
84299         }
84300     },
84301
84302     // Create the VML element/elements and append them to the DOM
84303     createSpriteElement: function(sprite) {
84304         var me = this,
84305             attr = sprite.attr,
84306             type = sprite.type,
84307             zoom = me.zoom,
84308             vml = sprite.vml || (sprite.vml = {}),
84309             round = Math.round,
84310             el = me.createNode('shape'),
84311             path, skew, textPath;
84312
84313         el.coordsize = zoom + ' ' + zoom;
84314         el.coordorigin = attr.coordorigin || "0 0";
84315         Ext.get(el).addCls(me.spriteCls);
84316         if (type == "text") {
84317             vml.path = path = me.createNode("path");
84318             path.textpathok = true;
84319             vml.textpath = textPath = me.createNode("textpath");
84320             textPath.on = true;
84321             el.appendChild(textPath);
84322             el.appendChild(path);
84323         }
84324         el.id = sprite.id;
84325         sprite.el = Ext.get(el);
84326         me.el.appendChild(el);
84327         if (type !== 'image') {
84328             skew = me.createNode("skew");
84329             skew.on = true;
84330             el.appendChild(skew);
84331             sprite.skew = skew;
84332         }
84333         sprite.matrix = Ext.create('Ext.draw.Matrix');
84334         sprite.bbox = {
84335             plain: null,
84336             transform: null
84337         };
84338         sprite.fireEvent("render", sprite);
84339         return sprite.el;
84340     },
84341
84342     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
84343     getBBox: function (sprite, isWithoutTransform) {
84344         var realPath = this["getPath" + sprite.type](sprite);
84345         if (isWithoutTransform) {
84346             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
84347             return sprite.bbox.plain;
84348         }
84349         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
84350         return sprite.bbox.transform;
84351     },
84352
84353     getBBoxText: function (sprite) {
84354         var vml = sprite.vml;
84355         return {
84356             x: vml.X + (vml.bbx || 0) - vml.W / 2,
84357             y: vml.Y - vml.H / 2,
84358             width: vml.W,
84359             height: vml.H
84360         };
84361     },
84362
84363     applyAttrs: function (sprite) {
84364         var me = this,
84365             vml = sprite.vml,
84366             group = sprite.group,
84367             spriteAttr = sprite.attr,
84368             el = sprite.el,
84369             dom = el.dom,
84370             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
84371
84372         if (group) {
84373             groups = [].concat(group);
84374             ln = groups.length;
84375             for (i = 0; i < ln; i++) {
84376                 group = groups[i];
84377                 me.getGroup(group).add(sprite);
84378             }
84379             delete sprite.group;
84380         }
84381         scrubbedAttrs = me.scrubAttrs(sprite) || {};
84382
84383         if (sprite.zIndexDirty) {
84384             me.setZIndex(sprite);
84385         }
84386
84387         // Apply minimum default attributes
84388         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
84389
84390         if (dom.href) {
84391             dom.href = scrubbedAttrs.href;
84392         }
84393         if (dom.title) {
84394             dom.title = scrubbedAttrs.title;
84395         }
84396         if (dom.target) {
84397             dom.target = scrubbedAttrs.target;
84398         }
84399         if (dom.cursor) {
84400             dom.cursor = scrubbedAttrs.cursor;
84401         }
84402
84403         // Change visibility
84404         if (sprite.dirtyHidden) {
84405             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
84406             sprite.dirtyHidden = false;
84407         }
84408
84409         // Update path
84410         if (sprite.dirtyPath) {
84411             if (sprite.type == "circle" || sprite.type == "ellipse") {
84412                 var cx = scrubbedAttrs.x,
84413                     cy = scrubbedAttrs.y,
84414                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
84415                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
84416                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
84417                             Math.round((cx - rx) * me.zoom),
84418                             Math.round((cy - ry) * me.zoom),
84419                             Math.round((cx + rx) * me.zoom),
84420                             Math.round((cy + ry) * me.zoom),
84421                             Math.round(cx * me.zoom));
84422                 sprite.dirtyPath = false;
84423             }
84424             else if (sprite.type !== "text") {
84425                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
84426                 dom.path = me.path2vml(scrubbedAttrs.path);
84427                 sprite.dirtyPath = false;
84428             }
84429         }
84430
84431         // Apply clipping
84432         if ("clip-rect" in scrubbedAttrs) {
84433             me.setClip(sprite, scrubbedAttrs);
84434         }
84435
84436         // Handle text (special handling required)
84437         if (sprite.type == "text") {
84438             me.setTextAttributes(sprite, scrubbedAttrs);
84439         }
84440
84441         // Handle fill and opacity
84442         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
84443             me.setFill(sprite, scrubbedAttrs);
84444         }
84445
84446         // Handle stroke (all fills require a stroke element)
84447         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
84448             me.setStroke(sprite, scrubbedAttrs);
84449         }
84450         
84451         //set styles
84452         style = spriteAttr.style;
84453         if (style) {
84454             el.setStyle(style);
84455         }
84456
84457         sprite.dirty = false;
84458     },
84459
84460     setZIndex: function(sprite) {
84461         if (sprite.el) {
84462             if (sprite.attr.zIndex != undefined) {
84463                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
84464             }
84465             sprite.zIndexDirty = false;
84466         }
84467     },
84468
84469     // Normalize all virtualized types into paths.
84470     setPaths: function(sprite, params) {
84471         var spriteAttr = sprite.attr;
84472         // Clear bbox cache
84473         sprite.bbox.plain = null;
84474         sprite.bbox.transform = null;
84475         if (sprite.type == 'circle') {
84476             spriteAttr.rx = spriteAttr.ry = params.r;
84477             return Ext.draw.Draw.ellipsePath(sprite);
84478         }
84479         else if (sprite.type == 'ellipse') {
84480             spriteAttr.rx = params.rx;
84481             spriteAttr.ry = params.ry;
84482             return Ext.draw.Draw.ellipsePath(sprite);
84483         }
84484         else if (sprite.type == 'rect' || sprite.type == 'image') {
84485             spriteAttr.rx = spriteAttr.ry = params.r;
84486             return Ext.draw.Draw.rectPath(sprite);
84487         }
84488         else if (sprite.type == 'path' && spriteAttr.path) {
84489             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
84490         }
84491         return false;
84492     },
84493
84494     setFill: function(sprite, params) {
84495         var me = this,
84496             el = sprite.el,
84497             dom = el.dom,
84498             fillEl = dom.getElementsByTagName('fill')[0],
84499             opacity, gradient, fillUrl, rotation, angle;
84500
84501         if (fillEl) {
84502             dom.removeChild(fillEl);
84503         } else {
84504             fillEl = me.createNode('fill');
84505         }
84506         if (Ext.isArray(params.fill)) {
84507             params.fill = params.fill[0];
84508         }
84509         if (sprite.type == 'image') {
84510             fillEl.on = true;
84511             fillEl.src = params.src;
84512             fillEl.type = "tile";
84513             fillEl.rotate = true;
84514         } else if (params.fill == "none") {
84515             fillEl.on = false;
84516         } else {
84517             if (typeof params.opacity == "number") {
84518                 fillEl.opacity = params.opacity;
84519             }
84520             if (typeof params["fill-opacity"] == "number") {
84521                 fillEl.opacity = params["fill-opacity"];
84522             }
84523             fillEl.on = true;
84524             if (typeof params.fill == "string") {
84525                 fillUrl = params.fill.match(me.fillUrlRe);
84526                 if (fillUrl) {
84527                     fillUrl = fillUrl[1];
84528                     // If the URL matches one of the registered gradients, render that gradient
84529                     if (fillUrl.charAt(0) == "#") {
84530                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
84531                     }
84532                     if (gradient) {
84533                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
84534                         rotation = params.rotation;
84535                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
84536                         // IE will flip the angle at 0 degrees...
84537                         if (angle === 0) {
84538                             angle = 180;
84539                         }
84540                         fillEl.angle = angle;
84541                         fillEl.type = "gradient";
84542                         fillEl.method = "sigma";
84543                         fillEl.colors = gradient.colors;
84544                     }
84545                     // Otherwise treat it as an image
84546                     else {
84547                         fillEl.src = fillUrl;
84548                         fillEl.type = "tile";
84549                         fillEl.rotate = true;
84550                     }
84551                 }
84552                 else {
84553                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
84554                     fillEl.src = "";
84555                     fillEl.type = "solid";
84556                 }
84557             }
84558         }
84559         dom.appendChild(fillEl);
84560     },
84561
84562     setStroke: function(sprite, params) {
84563         var me = this,
84564             el = sprite.el.dom,
84565             strokeEl = sprite.strokeEl,
84566             newStroke = false,
84567             width, opacity;
84568
84569         if (!strokeEl) {
84570             strokeEl = sprite.strokeEl = me.createNode("stroke");
84571             newStroke = true;
84572         }
84573         if (Ext.isArray(params.stroke)) {
84574             params.stroke = params.stroke[0];
84575         }
84576         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
84577             strokeEl.on = false;
84578         }
84579         else {
84580             strokeEl.on = true;
84581             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
84582                 // VML does NOT support a gradient stroke :(
84583                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
84584             }
84585             strokeEl.joinstyle = params["stroke-linejoin"];
84586             strokeEl.endcap = params["stroke-linecap"] || "round";
84587             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
84588             width = parseFloat(params["stroke-width"] || 1) * 0.75;
84589             opacity = params["stroke-opacity"] || 1;
84590             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
84591             if (Ext.isNumber(width) && width < 1) {
84592                 strokeEl.weight = 1;
84593                 strokeEl.opacity = opacity * width;
84594             }
84595             else {
84596                 strokeEl.weight = width;
84597                 strokeEl.opacity = opacity;
84598             }
84599         }
84600         if (newStroke) {
84601             el.appendChild(strokeEl);
84602         }
84603     },
84604
84605     setClip: function(sprite, params) {
84606         var me = this,
84607             el = sprite.el,
84608             clipEl = sprite.clipEl,
84609             rect = String(params["clip-rect"]).split(me.separatorRe);
84610         if (!clipEl) {
84611             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
84612             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
84613         }
84614         if (rect.length == 4) {
84615             rect[2] = +rect[2] + (+rect[0]);
84616             rect[3] = +rect[3] + (+rect[1]);
84617             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
84618             clipEl.setSize(me.el.width, me.el.height);
84619         }
84620         else {
84621             clipEl.setStyle("clip", "");
84622         }
84623     },
84624
84625     setTextAttributes: function(sprite, params) {
84626         var me = this,
84627             vml = sprite.vml,
84628             textStyle = vml.textpath.style,
84629             spanCacheStyle = me.span.style,
84630             zoom = me.zoom,
84631             round = Math.round,
84632             fontObj = {
84633                 fontSize: "font-size",
84634                 fontWeight: "font-weight",
84635                 fontStyle: "font-style"
84636             },
84637             fontProp,
84638             paramProp;
84639         if (sprite.dirtyFont) {
84640             if (params.font) {
84641                 textStyle.font = spanCacheStyle.font = params.font;
84642             }
84643             if (params["font-family"]) {
84644                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
84645                 spanCacheStyle.fontFamily = params["font-family"];
84646             }
84647
84648             for (fontProp in fontObj) {
84649                 paramProp = params[fontObj[fontProp]];
84650                 if (paramProp) {
84651                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
84652                 }
84653             }
84654
84655             me.setText(sprite, params.text);
84656             
84657             if (vml.textpath.string) {
84658                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
84659             }
84660             vml.W = me.span.offsetWidth;
84661             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
84662
84663             // text-anchor emulation
84664             if (params["text-anchor"] == "middle") {
84665                 textStyle["v-text-align"] = "center";
84666             }
84667             else if (params["text-anchor"] == "end") {
84668                 textStyle["v-text-align"] = "right";
84669                 vml.bbx = -Math.round(vml.W / 2);
84670             }
84671             else {
84672                 textStyle["v-text-align"] = "left";
84673                 vml.bbx = Math.round(vml.W / 2);
84674             }
84675         }
84676         vml.X = params.x;
84677         vml.Y = params.y;
84678         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);
84679         // Clear bbox cache
84680         sprite.bbox.plain = null;
84681         sprite.bbox.transform = null;
84682         sprite.dirtyFont = false;
84683     },
84684     
84685     setText: function(sprite, text) {
84686         sprite.vml.textpath.string = Ext.htmlDecode(text);
84687     },
84688
84689     hide: function() {
84690         this.el.hide();
84691     },
84692
84693     show: function() {
84694         this.el.show();
84695     },
84696
84697     hidePrim: function(sprite) {
84698         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
84699     },
84700
84701     showPrim: function(sprite) {
84702         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
84703     },
84704
84705     setSize: function(width, height) {
84706         var me = this;
84707         width = width || me.width;
84708         height = height || me.height;
84709         me.width = width;
84710         me.height = height;
84711
84712         if (me.el) {
84713             // Size outer div
84714             if (width != undefined) {
84715                 me.el.setWidth(width);
84716             }
84717             if (height != undefined) {
84718                 me.el.setHeight(height);
84719             }
84720
84721             // Handle viewBox sizing
84722             me.applyViewBox();
84723
84724             me.callParent(arguments);
84725         }
84726     },
84727
84728     setViewBox: function(x, y, width, height) {
84729         this.callParent(arguments);
84730         this.viewBox = {
84731             x: x,
84732             y: y,
84733             width: width,
84734             height: height
84735         };
84736         this.applyViewBox();
84737     },
84738
84739     /**
84740      * @private Using the current viewBox property and the surface's width and height, calculate the
84741      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
84742      */
84743     applyViewBox: function() {
84744         var me = this,
84745             viewBox = me.viewBox,
84746             width = me.width,
84747             height = me.height,
84748             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
84749             relativeHeight, relativeWidth, size;
84750
84751         if (viewBox && (width || height)) {
84752             viewBoxX = viewBox.x;
84753             viewBoxY = viewBox.y;
84754             viewBoxWidth = viewBox.width;
84755             viewBoxHeight = viewBox.height;
84756             relativeHeight = height / viewBoxHeight;
84757             relativeWidth = width / viewBoxWidth;
84758
84759             if (viewBoxWidth * relativeHeight < width) {
84760                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
84761             }
84762             if (viewBoxHeight * relativeWidth < height) {
84763                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
84764             }
84765
84766             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
84767
84768             me.viewBoxShift = {
84769                 dx: -viewBoxX,
84770                 dy: -viewBoxY,
84771                 scale: size
84772             };
84773             me.items.each(function(item) {
84774                 me.transform(item);
84775             });
84776         }
84777     },
84778
84779     onAdd: function(item) {
84780         this.callParent(arguments);
84781         if (this.el) {
84782             this.renderItem(item);
84783         }
84784     },
84785
84786     onRemove: function(sprite) {
84787         if (sprite.el) {
84788             sprite.el.remove();
84789             delete sprite.el;
84790         }
84791         this.callParent(arguments);
84792     },
84793
84794     // VML Node factory method (createNode)
84795     createNode : (function () {
84796         try {
84797             var doc = Ext.getDoc().dom;
84798             if (!doc.namespaces.rvml) {
84799                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
84800             }
84801             return function (tagName) {
84802                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
84803             };
84804         } catch (e) {
84805             return function (tagName) {
84806                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
84807             };
84808         }
84809     })(),
84810
84811     render: function (container) {
84812         var me = this,
84813             doc = Ext.getDoc().dom;
84814
84815         if (!me.el) {
84816             var el = doc.createElement("div");
84817             me.el = Ext.get(el);
84818             me.el.addCls(me.baseVmlCls);
84819
84820             // Measuring span (offscrren)
84821             me.span = doc.createElement("span");
84822             Ext.get(me.span).addCls(me.measureSpanCls);
84823             el.appendChild(me.span);
84824             me.el.setSize(me.width || 10, me.height || 10);
84825             container.appendChild(el);
84826             me.el.on({
84827                 scope: me,
84828                 mouseup: me.onMouseUp,
84829                 mousedown: me.onMouseDown,
84830                 mouseover: me.onMouseOver,
84831                 mouseout: me.onMouseOut,
84832                 mousemove: me.onMouseMove,
84833                 mouseenter: me.onMouseEnter,
84834                 mouseleave: me.onMouseLeave,
84835                 click: me.onClick
84836             });
84837         }
84838         me.renderAll();
84839     },
84840
84841     renderAll: function() {
84842         this.items.each(this.renderItem, this);
84843     },
84844
84845     redraw: function(sprite) {
84846         sprite.dirty = true;
84847         this.renderItem(sprite);
84848     },
84849
84850     renderItem: function (sprite) {
84851         // Does the surface element exist?
84852         if (!this.el) {
84853             return;
84854         }
84855
84856         // Create sprite element if necessary
84857         if (!sprite.el) {
84858             this.createSpriteElement(sprite);
84859         }
84860
84861         if (sprite.dirty) {
84862             this.applyAttrs(sprite);
84863             if (sprite.dirtyTransform) {
84864                 this.applyTransformations(sprite);
84865             }
84866         }
84867     },
84868
84869     rotationCompensation: function (deg, dx, dy) {
84870         var matrix = Ext.create('Ext.draw.Matrix');
84871         matrix.rotate(-deg, 0.5, 0.5);
84872         return {
84873             x: matrix.x(dx, dy),
84874             y: matrix.y(dx, dy)
84875         };
84876     },
84877
84878     extractTransform: function (sprite) {
84879         var me = this,
84880             matrix = Ext.create('Ext.draw.Matrix'), scale,
84881             transformstions, tranformationsLength,
84882             transform, i = 0,
84883             shift = me.viewBoxShift;
84884
84885         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
84886             i < tranformationsLength; i ++) {
84887             transform = transformstions[i];
84888             switch (transform.type) {
84889                 case 'translate' :
84890                     matrix.translate(transform.x, transform.y);
84891                     break;
84892                 case 'rotate':
84893                     matrix.rotate(transform.degrees, transform.x, transform.y);
84894                     break;
84895                 case 'scale':
84896                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
84897                     break;
84898             }
84899         }
84900
84901         if (shift) {
84902             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
84903             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
84904         }
84905         
84906         return sprite.matrix = matrix;
84907     },
84908
84909     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
84910         var me = this,
84911             matrix = sprite.matrix,
84912             dom = sprite.el.dom,
84913             style = dom.style,
84914             yFlipper = 1,
84915             flip = "",
84916             fill = dom.getElementsByTagName('fill')[0],
84917             kx = me.zoom / sx,
84918             ky = me.zoom / sy,
84919             rotationCompensation;
84920         if (!sx || !sy) {
84921             return;
84922         }
84923         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
84924         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
84925         if (rotate) {
84926             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
84927             dx = rotationCompensation.x;
84928             dy = rotationCompensation.y;
84929         }
84930         if (sx < 0) {
84931             flip += "x"
84932         }
84933         if (sy < 0) {
84934             flip += " y";
84935             yFlipper = -1;
84936         }
84937         style.flip = flip;
84938         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
84939         if (fill) {
84940             dom.removeChild(fill);
84941             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
84942             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
84943             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
84944             dom.appendChild(fill);
84945         }
84946     },
84947
84948     transform : function (sprite) {
84949         var me = this,
84950             el = sprite.el,
84951             skew = sprite.skew,
84952             dom = el.dom,
84953             domStyle = dom.style,
84954             matrix = me.extractTransform(sprite).clone(),
84955             split, zoom = me.zoom,
84956             fill = dom.getElementsByTagName('fill')[0],
84957             isPatt = !String(sprite.fill).indexOf("url("),
84958             offset, c;
84959
84960
84961         // Hide element while we transform
84962
84963         if (sprite.type != "image" && skew && !isPatt) {
84964             // matrix transform via VML skew
84965             skew.matrix = matrix.toString();
84966             // skew.offset = '32767,1' OK
84967             // skew.offset = '32768,1' Crash
84968             // M$, R U kidding??
84969             offset = matrix.offset();
84970             if (offset[0] > 32767) {
84971                 offset[0] = 32767;
84972             } else if (offset[0] < -32768) {
84973                 offset[0] = -32768
84974             }
84975             if (offset[1] > 32767) {
84976                 offset[1] = 32767;
84977             } else if (offset[1] < -32768) {
84978                 offset[1] = -32768
84979             }
84980             skew.offset = offset;
84981         } else {
84982             if (skew) {
84983                 skew.matrix = "1 0 0 1";
84984                 skew.offset = "0 0";
84985             }
84986             split = matrix.split();
84987             if (split.isSimple) {
84988                 domStyle.filter = '';
84989                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
84990             } else {
84991                 domStyle.filter = matrix.toFilter();
84992                 var bb = me.getBBox(sprite),
84993                     dx = bb.x - sprite.x,
84994                     dy = bb.y - sprite.y;
84995                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
84996                 if (fill) {
84997                     dom.removeChild(fill);
84998                     fill.position = dx + ' ' + dy;
84999                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
85000                     dom.appendChild(fill);
85001                 }
85002             }
85003         }
85004     },
85005
85006     createItem: function (config) {
85007         return Ext.create('Ext.draw.Sprite', config);
85008     },
85009
85010     getRegion: function() {
85011         return this.el.getRegion();
85012     },
85013
85014     addCls: function(sprite, className) {
85015         if (sprite && sprite.el) {
85016             sprite.el.addCls(className);
85017         }
85018     },
85019
85020     removeCls: function(sprite, className) {
85021         if (sprite && sprite.el) {
85022             sprite.el.removeCls(className);
85023         }
85024     },
85025
85026     /**
85027      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
85028      * to its corresponding VML attributes and store it for later use by individual sprites.
85029      * @param {Object} gradient
85030      */
85031     addGradient: function(gradient) {
85032         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
85033             colors = [],
85034             stops = Ext.create('Ext.util.MixedCollection');
85035
85036         // Build colors string
85037         stops.addAll(gradient.stops);
85038         stops.sortByKey("ASC", function(a, b) {
85039             a = parseInt(a, 10);
85040             b = parseInt(b, 10);
85041             return a > b ? 1 : (a < b ? -1 : 0);
85042         });
85043         stops.eachKey(function(k, v) {
85044             colors.push(k + "% " + v.color);
85045         });
85046
85047         gradients.add(gradient.id, {
85048             colors: colors.join(","),
85049             angle: gradient.angle
85050         });
85051     },
85052
85053     destroy: function() {
85054         var me = this;
85055         
85056         me.callParent(arguments);
85057         if (me.el) {
85058             me.el.remove();
85059         }
85060         delete me.el;
85061     }
85062 });
85063
85064 /**
85065  * @class Ext.fx.target.ElementCSS
85066  * @extends Ext.fx.target.Element
85067  * 
85068  * This class represents a animation target for an {@link Ext.Element} that supports CSS
85069  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
85070  * will be passed to the animation and the appropriate target will be created.
85071  */
85072 Ext.define('Ext.fx.target.ElementCSS', {
85073
85074     /* Begin Definitions */
85075
85076     extend: 'Ext.fx.target.Element',
85077
85078     /* End Definitions */
85079
85080     setAttr: function(targetData, isFirstFrame) {
85081         var cssArr = {
85082                 attrs: [],
85083                 duration: [],
85084                 easing: []
85085             },
85086             ln = targetData.length,
85087             attributes,
85088             attrs,
85089             attr,
85090             easing,
85091             duration,
85092             o,
85093             i,
85094             j,
85095             ln2;
85096         for (i = 0; i < ln; i++) {
85097             attrs = targetData[i];
85098             duration = attrs.duration;
85099             easing = attrs.easing;
85100             attrs = attrs.attrs;
85101             for (attr in attrs) {
85102                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
85103                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
85104                         return '-' + v.toLowerCase();
85105                     }));
85106                     cssArr.duration.push(duration + 'ms');
85107                     cssArr.easing.push(easing);
85108                 }
85109             }
85110         }
85111         attributes = cssArr.attrs.join(',');
85112         duration = cssArr.duration.join(',');
85113         easing = cssArr.easing.join(', ');
85114         for (i = 0; i < ln; i++) {
85115             attrs = targetData[i].attrs;
85116             for (attr in attrs) {
85117                 ln2 = attrs[attr].length;
85118                 for (j = 0; j < ln2; j++) {
85119                     o = attrs[attr][j];
85120                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
85121                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
85122                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
85123                     o[0].setStyle(attr, o[1]);
85124
85125                     // Must trigger reflow to make this get used as the start point for the transition that follows
85126                     if (isFirstFrame) {
85127                         o = o[0].dom.offsetWidth;
85128                     }
85129                     else {
85130                         // Remove transition properties when completed.
85131                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
85132                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
85133                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
85134                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
85135                         }, o[0], { single: true });
85136                     }
85137                 }
85138             }
85139         }
85140     }
85141 });
85142 /**
85143  * @class Ext.fx.target.CompositeElementCSS
85144  * @extends Ext.fx.target.CompositeElement
85145  * 
85146  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
85147  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
85148  * the group to be animated as a whole. In general this class will not be created directly, 
85149  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
85150  * will be created.
85151  */
85152 Ext.define('Ext.fx.target.CompositeElementCSS', {
85153
85154     /* Begin Definitions */
85155
85156     extend: 'Ext.fx.target.CompositeElement',
85157
85158     requires: ['Ext.fx.target.ElementCSS'],
85159
85160     /* End Definitions */
85161     setAttr: function() {
85162         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
85163     }
85164 });
85165 /**
85166  * @class Ext.layout.container.AbstractFit
85167  * @extends Ext.layout.container.Container
85168  * @private
85169  */
85170 Ext.define('Ext.layout.container.AbstractFit', {
85171
85172     /* Begin Definitions */
85173
85174     extend: 'Ext.layout.container.Container',
85175
85176     /* End Definitions */
85177
85178     itemCls: Ext.baseCSSPrefix + 'fit-item',
85179     targetCls: Ext.baseCSSPrefix + 'layout-fit',
85180     type: 'fit'
85181 });
85182 /**
85183  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
85184  * container. This class is intended to be extended or created via the `layout: 'fit'`
85185  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
85186  *
85187  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
85188  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
85189  * panels, only the first one will be displayed.
85190  *
85191  *     @example
85192  *     Ext.create('Ext.panel.Panel', {
85193  *         title: 'Fit Layout',
85194  *         width: 300,
85195  *         height: 150,
85196  *         layout:'fit',
85197  *         items: {
85198  *             title: 'Inner Panel',
85199  *             html: 'This is the inner panel content',
85200  *             bodyPadding: 20,
85201  *             border: false
85202  *         },
85203  *         renderTo: Ext.getBody()
85204  *     });
85205  */
85206 Ext.define('Ext.layout.container.Fit', {
85207
85208     /* Begin Definitions */
85209
85210     extend: 'Ext.layout.container.AbstractFit',
85211     alias: 'layout.fit',
85212     alternateClassName: 'Ext.layout.FitLayout',
85213     requires: ['Ext.layout.container.Box'],
85214
85215     /* End Definitions */
85216
85217     /**
85218      * @cfg {Object} defaultMargins
85219      * <p>If the individual contained items do not have a <tt>margins</tt>
85220      * property specified or margin specified via CSS, the default margins from this property will be
85221      * applied to each item.</p>
85222      * <br><p>This property may be specified as an object containing margins
85223      * to apply in the format:</p><pre><code>
85224 {
85225     top: (top margin),
85226     right: (right margin),
85227     bottom: (bottom margin),
85228     left: (left margin)
85229 }</code></pre>
85230      * <p>This property may also be specified as a string containing
85231      * space-separated, numeric margin values. The order of the sides associated
85232      * with each value matches the way CSS processes margin values:</p>
85233      * <div class="mdetail-params"><ul>
85234      * <li>If there is only one value, it applies to all sides.</li>
85235      * <li>If there are two values, the top and bottom borders are set to the
85236      * first value and the right and left are set to the second.</li>
85237      * <li>If there are three values, the top is set to the first value, the left
85238      * and right are set to the second, and the bottom is set to the third.</li>
85239      * <li>If there are four values, they apply to the top, right, bottom, and
85240      * left, respectively.</li>
85241      * </ul></div>
85242      * <p>Defaults to:</p><pre><code>
85243      * {top:0, right:0, bottom:0, left:0}
85244      * </code></pre>
85245      */
85246     defaultMargins: {
85247         top: 0,
85248         right: 0,
85249         bottom: 0,
85250         left: 0
85251     },
85252
85253     // @private
85254     onLayout : function() {
85255         var me = this,
85256             size,
85257             item,
85258             margins;
85259         me.callParent();
85260
85261         if (me.owner.items.length) {
85262             item = me.owner.items.get(0);
85263             margins = item.margins || me.defaultMargins;
85264             size = me.getLayoutTargetSize();
85265             size.width  -= margins.width;
85266             size.height -= margins.height;
85267             me.setItemBox(item, size);
85268
85269             // If any margins were configure either through the margins config, or in the CSS style,
85270             // Then positioning will be used.
85271             if (margins.left || margins.top) {
85272                 item.setPosition(margins.left, margins.top);
85273             }
85274         }
85275     },
85276
85277     getTargetBox : function() {
85278         return this.getLayoutTargetSize();
85279     },
85280
85281     setItemBox : function(item, box) {
85282         var me = this;
85283         if (item && box.height > 0) {
85284             if (!me.owner.isFixedWidth()) {
85285                box.width = undefined;
85286             }
85287             if (!me.owner.isFixedHeight()) {
85288                box.height = undefined;
85289             }
85290             me.setItemSize(item, box.width, box.height);
85291         }
85292     },
85293
85294     configureItem: function(item) {
85295
85296         // Card layout only controls dimensions which IT has controlled.
85297         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
85298         item.layoutManagedHeight = 0;
85299         item.layoutManagedWidth = 0;
85300
85301         this.callParent(arguments);
85302     }
85303 }, function() {
85304     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
85305     // (Defaulting to "0 0 0 0")
85306     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
85307 });
85308 /**
85309  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
85310  * @private
85311  */
85312 Ext.define('Ext.layout.container.AbstractCard', {
85313
85314     /* Begin Definitions */
85315
85316     extend: 'Ext.layout.container.Fit',
85317
85318     /* End Definitions */
85319
85320     type: 'card',
85321
85322     sizeAllCards: false,
85323
85324     hideInactive: true,
85325
85326     /**
85327      * @cfg {Boolean} deferredRender
85328      * True to render each contained item at the time it becomes active, false to render all contained items
85329      * as soon as the layout is rendered.  If there is a significant amount of content or
85330      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
85331      * true might improve performance.
85332      */
85333     deferredRender : false,
85334
85335     beforeLayout: function() {
85336         var me = this;
85337         me.getActiveItem();
85338         if (me.activeItem && me.deferredRender) {
85339             me.renderItems([me.activeItem], me.getRenderTarget());
85340             return true;
85341         }
85342         else {
85343             return this.callParent(arguments);
85344         }
85345     },
85346
85347     renderChildren: function () {
85348         if (!this.deferredRender) {
85349             this.getActiveItem();
85350             this.callParent();
85351         }
85352     },
85353
85354     onLayout: function() {
85355         var me = this,
85356             activeItem = me.activeItem,
85357             items = me.getVisibleItems(),
85358             ln = items.length,
85359             targetBox = me.getTargetBox(),
85360             i, item;
85361
85362         for (i = 0; i < ln; i++) {
85363             item = items[i];
85364             me.setItemBox(item, targetBox);
85365         }
85366
85367         if (!me.firstActivated && activeItem) {
85368             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
85369                 activeItem.fireEvent('activate', activeItem);
85370             }
85371             me.firstActivated = true;
85372         }
85373     },
85374
85375     isValidParent : function(item, target, position) {
85376         // Note: Card layout does not care about order within the target because only one is ever visible.
85377         // We only care whether the item is a direct child of the target.
85378         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
85379         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
85380     },
85381
85382     /**
85383      * Return the active (visible) component in the layout.
85384      * @returns {Ext.Component}
85385      */
85386     getActiveItem: function() {
85387         var me = this;
85388         if (!me.activeItem && me.owner) {
85389             me.activeItem = me.parseActiveItem(me.owner.activeItem);
85390         }
85391
85392         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
85393             return me.activeItem;
85394         }
85395
85396         return null;
85397     },
85398
85399     // @private
85400     parseActiveItem: function(item) {
85401         if (item && item.isComponent) {
85402             return item;
85403         }
85404         else if (typeof item == 'number' || item === undefined) {
85405             return this.getLayoutItems()[item || 0];
85406         }
85407         else {
85408             return this.owner.getComponent(item);
85409         }
85410     },
85411
85412     // @private
85413     configureItem: function(item, position) {
85414         this.callParent([item, position]);
85415         if (this.hideInactive && this.activeItem !== item) {
85416             item.hide();
85417         }
85418         else {
85419             item.show();
85420         }
85421     },
85422
85423     onRemove: function(component) {
85424         if (component === this.activeItem) {
85425             this.activeItem = null;
85426             if (this.owner.items.getCount() === 0) {
85427                 this.firstActivated = false;
85428             }
85429         }
85430     },
85431
85432     // @private
85433     getAnimation: function(newCard, owner) {
85434         var newAnim = (newCard || {}).cardSwitchAnimation;
85435         if (newAnim === false) {
85436             return false;
85437         }
85438         return newAnim || owner.cardSwitchAnimation;
85439     },
85440
85441     /**
85442      * Return the active (visible) component in the layout to the next card
85443      * @returns {Ext.Component} The next component or false.
85444      */
85445     getNext: function() {
85446         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85447         //should come back in 4.1
85448         var wrap = arguments[0];
85449         var items = this.getLayoutItems(),
85450             index = Ext.Array.indexOf(items, this.activeItem);
85451         return items[index + 1] || (wrap ? items[0] : false);
85452     },
85453
85454     /**
85455      * Sets the active (visible) component in the layout to the next card
85456      * @return {Ext.Component} the activated component or false when nothing activated.
85457      */
85458     next: function() {
85459         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85460         //should come back in 4.1
85461         var anim = arguments[0], wrap = arguments[1];
85462         return this.setActiveItem(this.getNext(wrap), anim);
85463     },
85464
85465     /**
85466      * Return the active (visible) component in the layout to the previous card
85467      * @returns {Ext.Component} The previous component or false.
85468      */
85469     getPrev: function() {
85470         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85471         //should come back in 4.1
85472         var wrap = arguments[0];
85473         var items = this.getLayoutItems(),
85474             index = Ext.Array.indexOf(items, this.activeItem);
85475         return items[index - 1] || (wrap ? items[items.length - 1] : false);
85476     },
85477
85478     /**
85479      * Sets the active (visible) component in the layout to the previous card
85480      * @return {Ext.Component} the activated component or false when nothing activated.
85481      */
85482     prev: function() {
85483         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85484         //should come back in 4.1
85485         var anim = arguments[0], wrap = arguments[1];
85486         return this.setActiveItem(this.getPrev(wrap), anim);
85487     }
85488 });
85489
85490 /**
85491  * Tracks what records are currently selected in a databound component.
85492  *
85493  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
85494  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
85495  * and provide a way to binding to the component.
85496  *
85497  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
85498  * subclasses to update the UI widget.
85499  */
85500 Ext.define('Ext.selection.Model', {
85501     extend: 'Ext.util.Observable',
85502     alternateClassName: 'Ext.AbstractSelectionModel',
85503     requires: ['Ext.data.StoreManager'],
85504     // lastSelected
85505
85506     /**
85507      * @cfg {String} mode
85508      * Mode of selection.  Valid values are:
85509      *
85510      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
85511      *   deselecting that item.  This is the default.
85512      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
85513      *   select or deselect an item.
85514      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
85515      */
85516
85517     /**
85518      * @cfg {Boolean} allowDeselect
85519      * Allow users to deselect a record in a DataView, List or Grid.
85520      * Only applicable when the {@link #mode} is 'SINGLE'.
85521      */
85522     allowDeselect: false,
85523
85524     /**
85525      * @property {Ext.util.MixedCollection} selected
85526      * A MixedCollection that maintains all of the currently selected records. Read-only.
85527      */
85528     selected: null,
85529
85530     /**
85531      * Prune records when they are removed from the store from the selection.
85532      * This is a private flag. For an example of its usage, take a look at
85533      * Ext.selection.TreeModel.
85534      * @private
85535      */
85536     pruneRemoved: true,
85537
85538     constructor: function(cfg) {
85539         var me = this;
85540
85541         cfg = cfg || {};
85542         Ext.apply(me, cfg);
85543
85544         me.addEvents(
85545             /**
85546              * @event
85547              * Fired after a selection change has occurred
85548              * @param {Ext.selection.Model} this
85549              * @param {Ext.data.Model[]} selected The selected records
85550              */
85551             'selectionchange'
85552         );
85553
85554         me.modes = {
85555             SINGLE: true,
85556             SIMPLE: true,
85557             MULTI: true
85558         };
85559
85560         // sets this.selectionMode
85561         me.setSelectionMode(cfg.mode || me.mode);
85562
85563         // maintains the currently selected records.
85564         me.selected = Ext.create('Ext.util.MixedCollection');
85565
85566         me.callParent(arguments);
85567     },
85568
85569     // binds the store to the selModel.
85570     bind : function(store, initial){
85571         var me = this;
85572
85573         if(!initial && me.store){
85574             if(store !== me.store && me.store.autoDestroy){
85575                 me.store.destroyStore();
85576             }else{
85577                 me.store.un("add", me.onStoreAdd, me);
85578                 me.store.un("clear", me.onStoreClear, me);
85579                 me.store.un("remove", me.onStoreRemove, me);
85580                 me.store.un("update", me.onStoreUpdate, me);
85581             }
85582         }
85583         if(store){
85584             store = Ext.data.StoreManager.lookup(store);
85585             store.on({
85586                 add: me.onStoreAdd,
85587                 clear: me.onStoreClear,
85588                 remove: me.onStoreRemove,
85589                 update: me.onStoreUpdate,
85590                 scope: me
85591             });
85592         }
85593         me.store = store;
85594         if(store && !initial) {
85595             me.refresh();
85596         }
85597     },
85598
85599     /**
85600      * Selects all records in the view.
85601      * @param {Boolean} suppressEvent True to suppress any select events
85602      */
85603     selectAll: function(suppressEvent) {
85604         var me = this,
85605             selections = me.store.getRange(),
85606             i = 0,
85607             len = selections.length,
85608             start = me.getSelection().length;
85609
85610         me.bulkChange = true;
85611         for (; i < len; i++) {
85612             me.doSelect(selections[i], true, suppressEvent);
85613         }
85614         delete me.bulkChange;
85615         // fire selection change only if the number of selections differs
85616         me.maybeFireSelectionChange(me.getSelection().length !== start);
85617     },
85618
85619     /**
85620      * Deselects all records in the view.
85621      * @param {Boolean} suppressEvent True to suppress any deselect events
85622      */
85623     deselectAll: function(suppressEvent) {
85624         var me = this,
85625             selections = me.getSelection(),
85626             i = 0,
85627             len = selections.length,
85628             start = me.getSelection().length;
85629
85630         me.bulkChange = true;
85631         for (; i < len; i++) {
85632             me.doDeselect(selections[i], suppressEvent);
85633         }
85634         delete me.bulkChange;
85635         // fire selection change only if the number of selections differs
85636         me.maybeFireSelectionChange(me.getSelection().length !== start);
85637     },
85638
85639     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
85640     // selection modes. Requires that an event be passed so that we can know
85641     // if user held ctrl or shift.
85642     selectWithEvent: function(record, e, keepExisting) {
85643         var me = this;
85644
85645         switch (me.selectionMode) {
85646             case 'MULTI':
85647                 if (e.ctrlKey && me.isSelected(record)) {
85648                     me.doDeselect(record, false);
85649                 } else if (e.shiftKey && me.lastFocused) {
85650                     me.selectRange(me.lastFocused, record, e.ctrlKey);
85651                 } else if (e.ctrlKey) {
85652                     me.doSelect(record, true, false);
85653                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
85654                     me.doSelect(record, keepExisting, false);
85655                 } else {
85656                     me.doSelect(record, false);
85657                 }
85658                 break;
85659             case 'SIMPLE':
85660                 if (me.isSelected(record)) {
85661                     me.doDeselect(record);
85662                 } else {
85663                     me.doSelect(record, true);
85664                 }
85665                 break;
85666             case 'SINGLE':
85667                 // if allowDeselect is on and this record isSelected, deselect it
85668                 if (me.allowDeselect && me.isSelected(record)) {
85669                     me.doDeselect(record);
85670                 // select the record and do NOT maintain existing selections
85671                 } else {
85672                     me.doSelect(record, false);
85673                 }
85674                 break;
85675         }
85676     },
85677
85678     /**
85679      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
85680      * All rows in between startRow and endRow are also selected.
85681      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
85682      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
85683      * @param {Boolean} [keepExisting] True to retain existing selections
85684      */
85685     selectRange : function(startRow, endRow, keepExisting, dir){
85686         var me = this,
85687             store = me.store,
85688             selectedCount = 0,
85689             i,
85690             tmp,
85691             dontDeselect,
85692             records = [];
85693
85694         if (me.isLocked()){
85695             return;
85696         }
85697
85698         if (!keepExisting) {
85699             me.deselectAll(true);
85700         }
85701
85702         if (!Ext.isNumber(startRow)) {
85703             startRow = store.indexOf(startRow);
85704         }
85705         if (!Ext.isNumber(endRow)) {
85706             endRow = store.indexOf(endRow);
85707         }
85708
85709         // swap values
85710         if (startRow > endRow){
85711             tmp = endRow;
85712             endRow = startRow;
85713             startRow = tmp;
85714         }
85715
85716         for (i = startRow; i <= endRow; i++) {
85717             if (me.isSelected(store.getAt(i))) {
85718                 selectedCount++;
85719             }
85720         }
85721
85722         if (!dir) {
85723             dontDeselect = -1;
85724         } else {
85725             dontDeselect = (dir == 'up') ? startRow : endRow;
85726         }
85727
85728         for (i = startRow; i <= endRow; i++){
85729             if (selectedCount == (endRow - startRow + 1)) {
85730                 if (i != dontDeselect) {
85731                     me.doDeselect(i, true);
85732                 }
85733             } else {
85734                 records.push(store.getAt(i));
85735             }
85736         }
85737         me.doMultiSelect(records, true);
85738     },
85739
85740     /**
85741      * Selects a record instance by record instance or index.
85742      * @param {Ext.data.Model[]/Number} records An array of records or an index
85743      * @param {Boolean} [keepExisting] True to retain existing selections
85744      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
85745      */
85746     select: function(records, keepExisting, suppressEvent) {
85747         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
85748         if (Ext.isDefined(records)) {
85749             this.doSelect(records, keepExisting, suppressEvent);
85750         }
85751     },
85752
85753     /**
85754      * Deselects a record instance by record instance or index.
85755      * @param {Ext.data.Model[]/Number} records An array of records or an index
85756      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
85757      */
85758     deselect: function(records, suppressEvent) {
85759         this.doDeselect(records, suppressEvent);
85760     },
85761
85762     doSelect: function(records, keepExisting, suppressEvent) {
85763         var me = this,
85764             record;
85765
85766         if (me.locked) {
85767             return;
85768         }
85769         if (typeof records === "number") {
85770             records = [me.store.getAt(records)];
85771         }
85772         if (me.selectionMode == "SINGLE" && records) {
85773             record = records.length ? records[0] : records;
85774             me.doSingleSelect(record, suppressEvent);
85775         } else {
85776             me.doMultiSelect(records, keepExisting, suppressEvent);
85777         }
85778     },
85779
85780     doMultiSelect: function(records, keepExisting, suppressEvent) {
85781         var me = this,
85782             selected = me.selected,
85783             change = false,
85784             i = 0,
85785             len, record;
85786
85787         if (me.locked) {
85788             return;
85789         }
85790
85791
85792         records = !Ext.isArray(records) ? [records] : records;
85793         len = records.length;
85794         if (!keepExisting && selected.getCount() > 0) {
85795             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
85796                 return;
85797             }
85798             // TODO - coalesce the selectionchange event in deselect w/the one below...
85799         }
85800
85801         function commit () {
85802             selected.add(record);
85803             change = true;
85804         }
85805
85806         for (; i < len; i++) {
85807             record = records[i];
85808             if (keepExisting && me.isSelected(record)) {
85809                 continue;
85810             }
85811             me.lastSelected = record;
85812
85813             me.onSelectChange(record, true, suppressEvent, commit);
85814         }
85815         me.setLastFocused(record, suppressEvent);
85816         // fire selchange if there was a change and there is no suppressEvent flag
85817         me.maybeFireSelectionChange(change && !suppressEvent);
85818     },
85819
85820     // records can be an index, a record or an array of records
85821     doDeselect: function(records, suppressEvent) {
85822         var me = this,
85823             selected = me.selected,
85824             i = 0,
85825             len, record,
85826             attempted = 0,
85827             accepted = 0;
85828
85829         if (me.locked) {
85830             return false;
85831         }
85832
85833         if (typeof records === "number") {
85834             records = [me.store.getAt(records)];
85835         } else if (!Ext.isArray(records)) {
85836             records = [records];
85837         }
85838
85839         function commit () {
85840             ++accepted;
85841             selected.remove(record);
85842         }
85843
85844         len = records.length;
85845
85846         for (; i < len; i++) {
85847             record = records[i];
85848             if (me.isSelected(record)) {
85849                 if (me.lastSelected == record) {
85850                     me.lastSelected = selected.last();
85851                 }
85852                 ++attempted;
85853                 me.onSelectChange(record, false, suppressEvent, commit);
85854             }
85855         }
85856
85857         // fire selchange if there was a change and there is no suppressEvent flag
85858         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
85859         return accepted === attempted;
85860     },
85861
85862     doSingleSelect: function(record, suppressEvent) {
85863         var me = this,
85864             changed = false,
85865             selected = me.selected;
85866
85867         if (me.locked) {
85868             return;
85869         }
85870         // already selected.
85871         // should we also check beforeselect?
85872         if (me.isSelected(record)) {
85873             return;
85874         }
85875
85876         function commit () {
85877             me.bulkChange = true;
85878             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
85879                 delete me.bulkChange;
85880                 return false;
85881             }
85882             delete me.bulkChange;
85883
85884             selected.add(record);
85885             me.lastSelected = record;
85886             changed = true;
85887         }
85888
85889         me.onSelectChange(record, true, suppressEvent, commit);
85890
85891         if (changed) {
85892             if (!suppressEvent) {
85893                 me.setLastFocused(record);
85894             }
85895             me.maybeFireSelectionChange(!suppressEvent);
85896         }
85897     },
85898
85899     /**
85900      * Sets a record as the last focused record. This does NOT mean
85901      * that the record has been selected.
85902      * @param {Ext.data.Model} record
85903      */
85904     setLastFocused: function(record, supressFocus) {
85905         var me = this,
85906             recordBeforeLast = me.lastFocused;
85907         me.lastFocused = record;
85908         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
85909     },
85910
85911     /**
85912      * Determines if this record is currently focused.
85913      * @param {Ext.data.Model} record
85914      */
85915     isFocused: function(record) {
85916         return record === this.getLastFocused();
85917     },
85918
85919
85920     // fire selection change as long as true is not passed
85921     // into maybeFireSelectionChange
85922     maybeFireSelectionChange: function(fireEvent) {
85923         var me = this;
85924         if (fireEvent && !me.bulkChange) {
85925             me.fireEvent('selectionchange', me, me.getSelection());
85926         }
85927     },
85928
85929     /**
85930      * Returns the last selected record.
85931      */
85932     getLastSelected: function() {
85933         return this.lastSelected;
85934     },
85935
85936     getLastFocused: function() {
85937         return this.lastFocused;
85938     },
85939
85940     /**
85941      * Returns an array of the currently selected records.
85942      * @return {Ext.data.Model[]} The selected records
85943      */
85944     getSelection: function() {
85945         return this.selected.getRange();
85946     },
85947
85948     /**
85949      * Returns the current selectionMode.
85950      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
85951      */
85952     getSelectionMode: function() {
85953         return this.selectionMode;
85954     },
85955
85956     /**
85957      * Sets the current selectionMode.
85958      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
85959      */
85960     setSelectionMode: function(selMode) {
85961         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
85962         // set to mode specified unless it doesnt exist, in that case
85963         // use single.
85964         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
85965     },
85966
85967     /**
85968      * Returns true if the selections are locked.
85969      * @return {Boolean}
85970      */
85971     isLocked: function() {
85972         return this.locked;
85973     },
85974
85975     /**
85976      * Locks the current selection and disables any changes from happening to the selection.
85977      * @param {Boolean} locked  True to lock, false to unlock.
85978      */
85979     setLocked: function(locked) {
85980         this.locked = !!locked;
85981     },
85982
85983     /**
85984      * Returns true if the specified row is selected.
85985      * @param {Ext.data.Model/Number} record The record or index of the record to check
85986      * @return {Boolean}
85987      */
85988     isSelected: function(record) {
85989         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
85990         return this.selected.indexOf(record) !== -1;
85991     },
85992
85993     /**
85994      * Returns true if there are any a selected records.
85995      * @return {Boolean}
85996      */
85997     hasSelection: function() {
85998         return this.selected.getCount() > 0;
85999     },
86000
86001     refresh: function() {
86002         var me = this,
86003             toBeSelected = [],
86004             oldSelections = me.getSelection(),
86005             len = oldSelections.length,
86006             selection,
86007             change,
86008             i = 0,
86009             lastFocused = this.getLastFocused();
86010
86011         // check to make sure that there are no records
86012         // missing after the refresh was triggered, prune
86013         // them from what is to be selected if so
86014         for (; i < len; i++) {
86015             selection = oldSelections[i];
86016             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
86017                 toBeSelected.push(selection);
86018             }
86019         }
86020
86021         // there was a change from the old selected and
86022         // the new selection
86023         if (me.selected.getCount() != toBeSelected.length) {
86024             change = true;
86025         }
86026
86027         me.clearSelections();
86028
86029         if (me.store.indexOf(lastFocused) !== -1) {
86030             // restore the last focus but supress restoring focus
86031             this.setLastFocused(lastFocused, true);
86032         }
86033
86034         if (toBeSelected.length) {
86035             // perform the selection again
86036             me.doSelect(toBeSelected, false, true);
86037         }
86038
86039         me.maybeFireSelectionChange(change);
86040     },
86041
86042     /**
86043      * A fast reset of the selections without firing events, updating the ui, etc.
86044      * For private usage only.
86045      * @private
86046      */
86047     clearSelections: function() {
86048         // reset the entire selection to nothing
86049         this.selected.clear();
86050         this.lastSelected = null;
86051         this.setLastFocused(null);
86052     },
86053
86054     // when a record is added to a store
86055     onStoreAdd: function() {
86056
86057     },
86058
86059     // when a store is cleared remove all selections
86060     // (if there were any)
86061     onStoreClear: function() {
86062         if (this.selected.getCount > 0) {
86063             this.clearSelections();
86064             this.maybeFireSelectionChange(true);
86065         }
86066     },
86067
86068     // prune records from the SelectionModel if
86069     // they were selected at the time they were
86070     // removed.
86071     onStoreRemove: function(store, record) {
86072         var me = this,
86073             selected = me.selected;
86074
86075         if (me.locked || !me.pruneRemoved) {
86076             return;
86077         }
86078
86079         if (selected.remove(record)) {
86080             if (me.lastSelected == record) {
86081                 me.lastSelected = null;
86082             }
86083             if (me.getLastFocused() == record) {
86084                 me.setLastFocused(null);
86085             }
86086             me.maybeFireSelectionChange(true);
86087         }
86088     },
86089
86090     /**
86091      * Returns the count of selected records.
86092      * @return {Number} The number of selected records
86093      */
86094     getCount: function() {
86095         return this.selected.getCount();
86096     },
86097
86098     // cleanup.
86099     destroy: function() {
86100
86101     },
86102
86103     // if records are updated
86104     onStoreUpdate: function() {
86105
86106     },
86107
86108     // @abstract
86109     onSelectChange: function(record, isSelected, suppressEvent) {
86110
86111     },
86112
86113     // @abstract
86114     onLastFocusChanged: function(oldFocused, newFocused) {
86115
86116     },
86117
86118     // @abstract
86119     onEditorKey: function(field, e) {
86120
86121     },
86122
86123     // @abstract
86124     bindComponent: function(cmp) {
86125
86126     }
86127 });
86128 /**
86129  * @class Ext.selection.DataViewModel
86130  * @ignore
86131  */
86132 Ext.define('Ext.selection.DataViewModel', {
86133     extend: 'Ext.selection.Model',
86134
86135     requires: ['Ext.util.KeyNav'],
86136
86137     deselectOnContainerClick: true,
86138
86139     /**
86140      * @cfg {Boolean} enableKeyNav
86141      *
86142      * Turns on/off keyboard navigation within the DataView.
86143      */
86144     enableKeyNav: true,
86145
86146     constructor: function(cfg){
86147         this.addEvents(
86148             /**
86149              * @event beforedeselect
86150              * Fired before a record is deselected. If any listener returns false, the
86151              * deselection is cancelled.
86152              * @param {Ext.selection.DataViewModel} this
86153              * @param {Ext.data.Model} record The deselected record
86154              */
86155             'beforedeselect',
86156
86157             /**
86158              * @event beforeselect
86159              * Fired before a record is selected. If any listener returns false, the
86160              * selection is cancelled.
86161              * @param {Ext.selection.DataViewModel} this
86162              * @param {Ext.data.Model} record The selected record
86163              */
86164             'beforeselect',
86165
86166             /**
86167              * @event deselect
86168              * Fired after a record is deselected
86169              * @param {Ext.selection.DataViewModel} this
86170              * @param  {Ext.data.Model} record The deselected record
86171              */
86172             'deselect',
86173
86174             /**
86175              * @event select
86176              * Fired after a record is selected
86177              * @param {Ext.selection.DataViewModel} this
86178              * @param  {Ext.data.Model} record The selected record
86179              */
86180             'select'
86181         );
86182         this.callParent(arguments);
86183     },
86184
86185     bindComponent: function(view) {
86186         var me = this,
86187             eventListeners = {
86188                 refresh: me.refresh,
86189                 scope: me
86190             };
86191
86192         me.view = view;
86193         me.bind(view.getStore());
86194
86195         view.on(view.triggerEvent, me.onItemClick, me);
86196         view.on(view.triggerCtEvent, me.onContainerClick, me);
86197
86198         view.on(eventListeners);
86199
86200         if (me.enableKeyNav) {
86201             me.initKeyNav(view);
86202         }
86203     },
86204
86205     onItemClick: function(view, record, item, index, e) {
86206         this.selectWithEvent(record, e);
86207     },
86208
86209     onContainerClick: function() {
86210         if (this.deselectOnContainerClick) {
86211             this.deselectAll();
86212         }
86213     },
86214
86215     initKeyNav: function(view) {
86216         var me = this;
86217
86218         if (!view.rendered) {
86219             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
86220             return;
86221         }
86222
86223         view.el.set({
86224             tabIndex: -1
86225         });
86226         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
86227             down: Ext.pass(me.onNavKey, [1], me),
86228             right: Ext.pass(me.onNavKey, [1], me),
86229             left: Ext.pass(me.onNavKey, [-1], me),
86230             up: Ext.pass(me.onNavKey, [-1], me),
86231             scope: me
86232         });
86233     },
86234
86235     onNavKey: function(step) {
86236         step = step || 1;
86237         var me = this,
86238             view = me.view,
86239             selected = me.getSelection()[0],
86240             numRecords = me.view.store.getCount(),
86241             idx;
86242
86243         if (selected) {
86244             idx = view.indexOf(view.getNode(selected)) + step;
86245         } else {
86246             idx = 0;
86247         }
86248
86249         if (idx < 0) {
86250             idx = numRecords - 1;
86251         } else if (idx >= numRecords) {
86252             idx = 0;
86253         }
86254
86255         me.select(idx);
86256     },
86257
86258     // Allow the DataView to update the ui
86259     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
86260         var me = this,
86261             view = me.view,
86262             eventName = isSelected ? 'select' : 'deselect';
86263
86264         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
86265                 commitFn() !== false) {
86266
86267             if (isSelected) {
86268                 view.onItemSelect(record);
86269             } else {
86270                 view.onItemDeselect(record);
86271             }
86272
86273             if (!suppressEvent) {
86274                 me.fireEvent(eventName, me, record);
86275             }
86276         }
86277     },
86278     
86279     destroy: function(){
86280         Ext.destroy(this.keyNav);
86281         this.callParent();
86282     }
86283 });
86284
86285 /**
86286  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
86287  * options, such as:
86288  *
86289  * - {@link #path}
86290  * - {@link #expires}
86291  * - {@link #domain}
86292  * - {@link #secure}
86293  *
86294  * Example:
86295  *
86296  *     Ext.create('Ext.state.CookieProvider', {
86297  *         path: "/cgi-bin/",
86298  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
86299  *         domain: "sencha.com"
86300  *     });
86301  *
86302  *     Ext.state.Manager.setProvider(cp);
86303  *
86304  * @constructor
86305  * Creates a new CookieProvider.
86306  * @param {Object} config (optional) Config object.
86307  * @return {Object}
86308  */
86309 Ext.define('Ext.state.CookieProvider', {
86310     extend: 'Ext.state.Provider',
86311
86312     /**
86313      * @cfg {String} path
86314      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
86315      */
86316
86317     /**
86318      * @cfg {Date} expires
86319      * The cookie expiration date. Defaults to 7 days from now.
86320      */
86321
86322     /**
86323      * @cfg {String} domain
86324      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
86325      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
86326      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
86327      * the 'www' like 'www.sencha.com'.
86328      */
86329
86330     /**
86331      * @cfg {Boolean} [secure=false]
86332      * True if the site is using SSL
86333      */
86334
86335     /**
86336      * Creates a new CookieProvider.
86337      * @param {Object} [config] Config object.
86338      */
86339     constructor : function(config){
86340         var me = this;
86341         me.path = "/";
86342         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
86343         me.domain = null;
86344         me.secure = false;
86345         me.callParent(arguments);
86346         me.state = me.readCookies();
86347     },
86348
86349     // private
86350     set : function(name, value){
86351         var me = this;
86352
86353         if(typeof value == "undefined" || value === null){
86354             me.clear(name);
86355             return;
86356         }
86357         me.setCookie(name, value);
86358         me.callParent(arguments);
86359     },
86360
86361     // private
86362     clear : function(name){
86363         this.clearCookie(name);
86364         this.callParent(arguments);
86365     },
86366
86367     // private
86368     readCookies : function(){
86369         var cookies = {},
86370             c = document.cookie + ";",
86371             re = /\s?(.*?)=(.*?);/g,
86372             prefix = this.prefix,
86373             len = prefix.length,
86374             matches,
86375             name,
86376             value;
86377
86378         while((matches = re.exec(c)) != null){
86379             name = matches[1];
86380             value = matches[2];
86381             if (name && name.substring(0, len) == prefix){
86382                 cookies[name.substr(len)] = this.decodeValue(value);
86383             }
86384         }
86385         return cookies;
86386     },
86387
86388     // private
86389     setCookie : function(name, value){
86390         var me = this;
86391
86392         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
86393            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
86394            ((me.path == null) ? "" : ("; path=" + me.path)) +
86395            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86396            ((me.secure == true) ? "; secure" : "");
86397     },
86398
86399     // private
86400     clearCookie : function(name){
86401         var me = this;
86402
86403         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
86404            ((me.path == null) ? "" : ("; path=" + me.path)) +
86405            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86406            ((me.secure == true) ? "; secure" : "");
86407     }
86408 });
86409
86410 /**
86411  * @class Ext.state.LocalStorageProvider
86412  * @extends Ext.state.Provider
86413  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
86414  * If the browser does not support local storage, an exception will be thrown upon instantiating
86415  * this class.
86416  */
86417
86418 Ext.define('Ext.state.LocalStorageProvider', {
86419     /* Begin Definitions */
86420     
86421     extend: 'Ext.state.Provider',
86422     
86423     alias: 'state.localstorage',
86424     
86425     /* End Definitions */
86426    
86427     constructor: function(){
86428         var me = this;
86429         me.callParent(arguments);
86430         me.store = me.getStorageObject();
86431         me.state = me.readLocalStorage();
86432     },
86433     
86434     readLocalStorage: function(){
86435         var store = this.store,
86436             i = 0,
86437             len = store.length,
86438             prefix = this.prefix,
86439             prefixLen = prefix.length,
86440             data = {},
86441             key;
86442             
86443         for (; i < len; ++i) {
86444             key = store.key(i);
86445             if (key.substring(0, prefixLen) == prefix) {
86446                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
86447             }            
86448         }
86449         return data;
86450     },
86451     
86452     set : function(name, value){
86453         var me = this;
86454         
86455         me.clear(name);
86456         if (typeof value == "undefined" || value === null) {
86457             return;
86458         }
86459         me.store.setItem(me.prefix + name, me.encodeValue(value));
86460         me.callParent(arguments);
86461     },
86462
86463     // private
86464     clear : function(name){
86465         this.store.removeItem(this.prefix + name);
86466         this.callParent(arguments);
86467     },
86468     
86469     getStorageObject: function(){
86470         try {
86471             var supports = 'localStorage' in window && window['localStorage'] !== null;
86472             if (supports) {
86473                 return window.localStorage;
86474             }
86475         } catch (e) {
86476             return false;
86477         }
86478     }    
86479 });
86480
86481 /**
86482  * Represents a 2D point with x and y properties, useful for comparison and instantiation
86483  * from an event:
86484  *
86485  *     var point = Ext.util.Point.fromEvent(e);
86486  *
86487  */
86488 Ext.define('Ext.util.Point', {
86489
86490     /* Begin Definitions */
86491     extend: 'Ext.util.Region',
86492
86493     statics: {
86494
86495         /**
86496          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
86497          * @static
86498          * @param {Event} e The event
86499          * @return {Ext.util.Point}
86500          */
86501         fromEvent: function(e) {
86502             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
86503             return new this(e.pageX, e.pageY);
86504         }
86505     },
86506
86507     /* End Definitions */
86508
86509     /**
86510      * Creates a point from two coordinates.
86511      * @param {Number} x X coordinate.
86512      * @param {Number} y Y coordinate.
86513      */
86514     constructor: function(x, y) {
86515         this.callParent([y, x, y, x]);
86516     },
86517
86518     /**
86519      * Returns a human-eye-friendly string that represents this point,
86520      * useful for debugging
86521      * @return {String}
86522      */
86523     toString: function() {
86524         return "Point[" + this.x + "," + this.y + "]";
86525     },
86526
86527     /**
86528      * Compare this point and another point
86529      * @param {Ext.util.Point/Object} The point to compare with, either an instance
86530      * of Ext.util.Point or an object with left and top properties
86531      * @return {Boolean} Returns whether they are equivalent
86532      */
86533     equals: function(p) {
86534         return (this.x == p.x && this.y == p.y);
86535     },
86536
86537     /**
86538      * Whether the given point is not away from this point within the given threshold amount.
86539      * @param {Ext.util.Point/Object} p The point to check with, either an instance
86540      * of Ext.util.Point or an object with left and top properties
86541      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
86542      * @return {Boolean}
86543      */
86544     isWithin: function(p, threshold) {
86545         if (!Ext.isObject(threshold)) {
86546             threshold = {
86547                 x: threshold,
86548                 y: threshold
86549             };
86550         }
86551
86552         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
86553                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
86554     },
86555
86556     /**
86557      * Compare this point with another point when the x and y values of both points are rounded. E.g:
86558      * [100.3,199.8] will equals to [100, 200]
86559      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
86560      * of Ext.util.Point or an object with x and y properties
86561      * @return {Boolean}
86562      */
86563     roundedEquals: function(p) {
86564         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
86565     }
86566 }, function() {
86567     /**
86568      * @method
86569      * Alias for {@link #translateBy}
86570      * @alias Ext.util.Region#translateBy
86571      */
86572     this.prototype.translate = Ext.util.Region.prototype.translateBy;
86573 });
86574
86575 /**
86576  * @class Ext.LoadMask
86577  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
86578  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
86579  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
86580  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
86581  * the Store's loading process.</p>
86582  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
86583  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
86584  * <p>Example usage:</p>
86585  * <pre><code>
86586 // Basic mask:
86587 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
86588 myMask.show();
86589 </code></pre>
86590
86591  */
86592
86593 Ext.define('Ext.LoadMask', {
86594
86595     extend: 'Ext.Component',
86596
86597     alias: 'widget.loadmask',
86598
86599     /* Begin Definitions */
86600
86601     mixins: {
86602         floating: 'Ext.util.Floating'
86603     },
86604
86605     uses: ['Ext.data.StoreManager'],
86606
86607     /* End Definitions */
86608
86609     /**
86610      * @cfg {Ext.data.Store} store
86611      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
86612      * hidden on either load success, or load fail.
86613      */
86614
86615     /**
86616      * @cfg {String} msg
86617      * The text to display in a centered loading message box.
86618      */
86619     msg : 'Loading...',
86620     /**
86621      * @cfg {String} [msgCls="x-mask-loading"]
86622      * The CSS class to apply to the loading message element.
86623      */
86624     msgCls : Ext.baseCSSPrefix + 'mask-loading',
86625     
86626     /**
86627      * @cfg {Boolean} useMsg
86628      * Whether or not to use a loading message class or simply mask the bound element.
86629      */
86630     useMsg: true,
86631
86632     /**
86633      * Read-only. True if the mask is currently disabled so that it will not be displayed
86634      * @type Boolean
86635      */
86636     disabled: false,
86637
86638     baseCls: Ext.baseCSSPrefix + 'mask-msg',
86639
86640     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
86641
86642     // Private. The whole point is that there's a mask.
86643     modal: true,
86644
86645     // Private. Obviously, it's floating.
86646     floating: {
86647         shadow: 'frame'
86648     },
86649
86650     // Private. Masks are not focusable
86651     focusOnToFront: false,
86652
86653     /**
86654      * Creates new LoadMask.
86655      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
86656      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
86657      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
86658      * and the mask only be visible when the Component is.</p>
86659      * @param {Object} [config] The config object
86660      */
86661     constructor : function(el, config) {
86662         var me = this;
86663
86664         // If a Component passed, bind to it.
86665         if (el.isComponent) {
86666             me.ownerCt = el;
86667             me.bindComponent(el);
86668         }
86669         // Create a dumy Component encapsulating the specified Element
86670         else {
86671             me.ownerCt = new Ext.Component({
86672                 el: Ext.get(el),
86673                 rendered: true,
86674                 componentLayoutCounter: 1
86675             });
86676             me.container = el;
86677         }
86678         me.callParent([config]);
86679
86680         if (me.store) {
86681             me.bindStore(me.store, true);
86682         }
86683         me.renderData = {
86684             msgCls: me.msgCls
86685         };
86686         me.renderSelectors = {
86687             msgEl: 'div'
86688         };
86689     },
86690
86691     bindComponent: function(comp) {
86692         this.mon(comp, {
86693             resize: this.onComponentResize,
86694             scope: this
86695         });
86696     },
86697
86698     afterRender: function() {
86699         this.callParent(arguments);
86700         this.container = this.floatParent.getContentTarget();
86701     },
86702
86703     /**
86704      * @private
86705      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
86706      */
86707     onComponentResize: function() {
86708         var me = this;
86709         if (me.rendered && me.isVisible()) {
86710             me.toFront();
86711             me.center();
86712         }
86713     },
86714
86715     /**
86716      * Changes the data store bound to this LoadMask.
86717      * @param {Ext.data.Store} store The store to bind to this LoadMask
86718      */
86719     bindStore : function(store, initial) {
86720         var me = this;
86721
86722         if (!initial && me.store) {
86723             me.mun(me.store, {
86724                 scope: me,
86725                 beforeload: me.onBeforeLoad,
86726                 load: me.onLoad,
86727                 exception: me.onLoad
86728             });
86729             if (!store) {
86730                 me.store = null;
86731             }
86732         }
86733         if (store) {
86734             store = Ext.data.StoreManager.lookup(store);
86735             me.mon(store, {
86736                 scope: me,
86737                 beforeload: me.onBeforeLoad,
86738                 load: me.onLoad,
86739                 exception: me.onLoad
86740             });
86741
86742         }
86743         me.store = store;
86744         if (store && store.isLoading()) {
86745             me.onBeforeLoad();
86746         }
86747     },
86748
86749     onDisable : function() {
86750         this.callParent(arguments);
86751         if (this.loading) {
86752             this.onLoad();
86753         }
86754     },
86755
86756     // private
86757     onBeforeLoad : function() {
86758         var me = this,
86759             owner = me.ownerCt || me.floatParent,
86760             origin;
86761         if (!this.disabled) {
86762             // If the owning Component has not been layed out, defer so that the ZIndexManager
86763             // gets to read its layed out size when sizing the modal mask
86764             if (owner.componentLayoutCounter) {
86765                 Ext.Component.prototype.show.call(me);
86766             } else {
86767                 // The code below is a 'run-once' interceptor.
86768                 origin = owner.afterComponentLayout;
86769                 owner.afterComponentLayout = function() {
86770                     owner.afterComponentLayout = origin;
86771                     origin.apply(owner, arguments);
86772                     if(me.loading) {
86773                         Ext.Component.prototype.show.call(me);
86774                     }
86775                 };
86776             }
86777         }
86778     },
86779
86780     onHide: function(){
86781         var me = this;
86782         me.callParent(arguments);
86783         me.showOnParentShow = true;
86784     },
86785
86786     onShow: function() {
86787         var me = this,
86788             msgEl = me.msgEl;
86789             
86790         me.callParent(arguments);
86791         me.loading = true;
86792         if (me.useMsg) {
86793             msgEl.show().update(me.msg);
86794         } else {
86795             msgEl.parent().hide();
86796         }
86797     },
86798
86799     afterShow: function() {
86800         this.callParent(arguments);
86801         this.center();
86802     },
86803
86804     // private
86805     onLoad : function() {
86806         this.loading = false;
86807         Ext.Component.prototype.hide.call(this);
86808     }
86809 });
86810 /**
86811  * @class Ext.view.AbstractView
86812  * @extends Ext.Component
86813  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
86814  * @private
86815  */
86816 Ext.define('Ext.view.AbstractView', {
86817     extend: 'Ext.Component',
86818     alternateClassName: 'Ext.view.AbstractView',
86819     requires: [
86820         'Ext.LoadMask',
86821         'Ext.data.StoreManager',
86822         'Ext.CompositeElementLite',
86823         'Ext.DomQuery',
86824         'Ext.selection.DataViewModel'
86825     ],
86826
86827     inheritableStatics: {
86828         getRecord: function(node) {
86829             return this.getBoundView(node).getRecord(node);
86830         },
86831
86832         getBoundView: function(node) {
86833             return Ext.getCmp(node.boundView);
86834         }
86835     },
86836
86837     /**
86838      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
86839      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
86840      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
86841      */
86842     /**
86843      * @cfg {Ext.data.Store} store (required)
86844      * The {@link Ext.data.Store} to bind this DataView to.
86845      */
86846
86847     /**
86848      * @cfg {Boolean} deferInitialRefresh
86849      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
86850      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
86851      * by the expensive update of the view structure.</p>
86852      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
86853      * <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.
86854      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
86855      */
86856     deferInitialRefresh: true,
86857
86858     /**
86859      * @cfg {String} itemSelector (required)
86860      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
86861      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
86862      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
86863      * only be one root level element that matches the selector for each record.
86864      */
86865
86866     /**
86867      * @cfg {String} itemCls
86868      * Specifies the class to be assigned to each element in the view when used in conjunction with the
86869      * {@link #itemTpl} configuration.
86870      */
86871     itemCls: Ext.baseCSSPrefix + 'dataview-item',
86872
86873     /**
86874      * @cfg {String/String[]/Ext.XTemplate} itemTpl
86875      * The inner portion of the item template to be rendered. Follows an XTemplate
86876      * structure and will be placed inside of a tpl.
86877      */
86878
86879     /**
86880      * @cfg {String} overItemCls
86881      * A CSS class to apply to each item in the view on mouseover.
86882      * Ensure {@link #trackOver} is set to `true` to make use of this.
86883      */
86884
86885     /**
86886      * @cfg {String} loadingText
86887      * A string to display during data load operations.  If specified, this text will be
86888      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
86889      * contents will continue to display normally until the new data is loaded and the contents are replaced.
86890      */
86891     loadingText: 'Loading...',
86892
86893     /**
86894      * @cfg {Boolean/Object} loadMask
86895      * False to disable a load mask from displaying will the view is loading. This can also be a
86896      * {@link Ext.LoadMask} configuration object.
86897      */
86898     loadMask: true,
86899
86900     /**
86901      * @cfg {String} loadingCls
86902      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
86903      */
86904
86905     /**
86906      * @cfg {Boolean} loadingUseMsg
86907      * Whether or not to use the loading message.
86908      * @private
86909      */
86910     loadingUseMsg: true,
86911
86912
86913     /**
86914      * @cfg {Number} loadingHeight
86915      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
86916      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
86917      * loading mask is applied and there are no other contents in the data view.
86918      */
86919
86920     /**
86921      * @cfg {String} [selectedItemCls='x-view-selected']
86922      * A CSS class to apply to each selected item in the view.
86923      */
86924     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
86925
86926     /**
86927      * @cfg {String} emptyText
86928      * The text to display in the view when there is no data to display.
86929      * Note that when using local data the emptyText will not be displayed unless you set
86930      * the {@link #deferEmptyText} option to false.
86931      */
86932     emptyText: "",
86933
86934     /**
86935      * @cfg {Boolean} deferEmptyText
86936      * True to defer emptyText being applied until the store's first load.
86937      */
86938     deferEmptyText: true,
86939
86940     /**
86941      * @cfg {Boolean} trackOver
86942      * True to enable mouseenter and mouseleave events
86943      */
86944     trackOver: false,
86945
86946     /**
86947      * @cfg {Boolean} blockRefresh
86948      * Set this to true to ignore datachanged events on the bound store. This is useful if
86949      * you wish to provide custom transition animations via a plugin
86950      */
86951     blockRefresh: false,
86952
86953     /**
86954      * @cfg {Boolean} disableSelection
86955      * True to disable selection within the DataView. This configuration will lock the selection model
86956      * that the DataView uses.
86957      */
86958
86959
86960     //private
86961     last: false,
86962
86963     triggerEvent: 'itemclick',
86964     triggerCtEvent: 'containerclick',
86965
86966     addCmpEvents: function() {
86967
86968     },
86969
86970     // private
86971     initComponent : function(){
86972         var me = this,
86973             isDef = Ext.isDefined,
86974             itemTpl = me.itemTpl,
86975             memberFn = {};
86976
86977         if (itemTpl) {
86978             if (Ext.isArray(itemTpl)) {
86979                 // string array
86980                 itemTpl = itemTpl.join('');
86981             } else if (Ext.isObject(itemTpl)) {
86982                 // tpl instance
86983                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
86984                 itemTpl = itemTpl.html;
86985             }
86986
86987             if (!me.itemSelector) {
86988                 me.itemSelector = '.' + me.itemCls;
86989             }
86990
86991             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
86992             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
86993         }
86994
86995
86996         me.callParent();
86997         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
86998             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
86999         }
87000
87001
87002         me.addEvents(
87003             /**
87004              * @event beforerefresh
87005              * Fires before the view is refreshed
87006              * @param {Ext.view.View} this The DataView object
87007              */
87008             'beforerefresh',
87009             /**
87010              * @event refresh
87011              * Fires when the view is refreshed
87012              * @param {Ext.view.View} this The DataView object
87013              */
87014             'refresh',
87015             /**
87016              * @event viewready
87017              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
87018              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
87019              * for selection until this event fires.
87020              * @param {Ext.view.View} this
87021              */
87022             'viewready',
87023             /**
87024              * @event itemupdate
87025              * Fires when the node associated with an individual record is updated
87026              * @param {Ext.data.Model} record The model instance
87027              * @param {Number} index The index of the record/node
87028              * @param {HTMLElement} node The node that has just been updated
87029              */
87030             'itemupdate',
87031             /**
87032              * @event itemadd
87033              * Fires when the nodes associated with an recordset have been added to the underlying store
87034              * @param {Ext.data.Model[]} records The model instance
87035              * @param {Number} index The index at which the set of record/nodes starts
87036              * @param {HTMLElement[]} node The node that has just been updated
87037              */
87038             'itemadd',
87039             /**
87040              * @event itemremove
87041              * Fires when the node associated with an individual record is removed
87042              * @param {Ext.data.Model} record The model instance
87043              * @param {Number} index The index of the record/node
87044              */
87045             'itemremove'
87046         );
87047
87048         me.addCmpEvents();
87049
87050         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
87051         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
87052         me.all = new Ext.CompositeElementLite();
87053     },
87054
87055     onRender: function() {
87056         var me = this,
87057             mask = me.loadMask,
87058             cfg = {
87059                 msg: me.loadingText,
87060                 msgCls: me.loadingCls,
87061                 useMsg: me.loadingUseMsg
87062             };
87063
87064         me.callParent(arguments);
87065
87066         if (mask) {
87067             // either a config object
87068             if (Ext.isObject(mask)) {
87069                 cfg = Ext.apply(cfg, mask);
87070             }
87071             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
87072             // If this DataView is floating, then mask this DataView.
87073             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
87074             // LoadMask captures the element upon render.
87075             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
87076             me.loadMask.on({
87077                 scope: me,
87078                 beforeshow: me.onMaskBeforeShow,
87079                 hide: me.onMaskHide
87080             });
87081         }
87082     },
87083
87084     onMaskBeforeShow: function(){
87085         var loadingHeight = this.loadingHeight;
87086         
87087         this.getSelectionModel().deselectAll();
87088         if (loadingHeight) {
87089             this.setCalculatedSize(undefined, loadingHeight);
87090         }
87091     },
87092
87093     onMaskHide: function(){
87094         var me = this;
87095         
87096         if (!me.destroying && me.loadingHeight) {
87097             me.setHeight(me.height);
87098         }
87099     },
87100
87101     afterRender: function() {
87102         this.callParent(arguments);
87103
87104         // Init the SelectionModel after any on('render') listeners have been added.
87105         // Drag plugins create a DragDrop instance in a render listener, and that needs
87106         // to see an itemmousedown event first.
87107         this.getSelectionModel().bindComponent(this);
87108     },
87109
87110     /**
87111      * Gets the selection model for this view.
87112      * @return {Ext.selection.Model} The selection model
87113      */
87114     getSelectionModel: function(){
87115         var me = this,
87116             mode = 'SINGLE';
87117
87118         if (!me.selModel) {
87119             me.selModel = {};
87120         }
87121
87122         if (me.simpleSelect) {
87123             mode = 'SIMPLE';
87124         } else if (me.multiSelect) {
87125             mode = 'MULTI';
87126         }
87127
87128         Ext.applyIf(me.selModel, {
87129             allowDeselect: me.allowDeselect,
87130             mode: mode
87131         });
87132
87133         if (!me.selModel.events) {
87134             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
87135         }
87136
87137         if (!me.selModel.hasRelaySetup) {
87138             me.relayEvents(me.selModel, [
87139                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
87140             ]);
87141             me.selModel.hasRelaySetup = true;
87142         }
87143
87144         // lock the selection model if user
87145         // has disabled selection
87146         if (me.disableSelection) {
87147             me.selModel.locked = true;
87148         }
87149
87150         return me.selModel;
87151     },
87152
87153     /**
87154      * Refreshes the view by reloading the data from the store and re-rendering the template.
87155      */
87156     refresh: function() {
87157         var me = this,
87158             el,
87159             records;
87160
87161         if (!me.rendered || me.isDestroyed) {
87162             return;
87163         }
87164
87165         me.fireEvent('beforerefresh', me);
87166         el = me.getTargetEl();
87167         records = me.store.getRange();
87168
87169         el.update('');
87170         if (records.length < 1) {
87171             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
87172                 el.update(me.emptyText);
87173             }
87174             me.all.clear();
87175         } else {
87176             me.tpl.overwrite(el, me.collectData(records, 0));
87177             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
87178             me.updateIndexes(0);
87179         }
87180
87181         me.selModel.refresh();
87182         me.hasSkippedEmptyText = true;
87183         me.fireEvent('refresh', me);
87184
87185         // Upon first refresh, fire the viewready event.
87186         // Reconfiguring the grid "renews" this event.
87187         if (!me.viewReady) {
87188             // Fire an event when deferred content becomes available.
87189             // This supports grid Panel's deferRowRender capability
87190             me.viewReady = true;
87191             me.fireEvent('viewready', me);
87192         }
87193     },
87194
87195     /**
87196      * Function which can be overridden to provide custom formatting for each Record that is used by this
87197      * DataView's {@link #tpl template} to render each node.
87198      * @param {Object/Object[]} data The raw data object that was used to create the Record.
87199      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
87200      * @param {Ext.data.Model} record The Record being prepared for rendering.
87201      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
87202      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
87203      */
87204     prepareData: function(data, index, record) {
87205         if (record) {
87206             Ext.apply(data, record.getAssociatedData());
87207         }
87208         return data;
87209     },
87210
87211     /**
87212      * <p>Function which can be overridden which returns the data object passed to this
87213      * DataView's {@link #tpl template} to render the whole DataView.</p>
87214      * <p>This is usually an Array of data objects, each element of which is processed by an
87215      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
87216      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
87217      * provide non-repeating data such as headings, totals etc.</p>
87218      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
87219      * @param {Number} startIndex the index number of the Record being prepared for rendering.
87220      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
87221      * contain <i>named</i> properties.
87222      */
87223     collectData : function(records, startIndex){
87224         var r = [],
87225             i = 0,
87226             len = records.length,
87227             record;
87228
87229         for(; i < len; i++){
87230             record = records[i];
87231             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
87232         }
87233         return r;
87234     },
87235
87236     // private
87237     bufferRender : function(records, index){
87238         var div = document.createElement('div');
87239         this.tpl.overwrite(div, this.collectData(records, index));
87240         return Ext.query(this.getItemSelector(), div);
87241     },
87242
87243     // private
87244     onUpdate : function(ds, record){
87245         var me = this,
87246             index = me.store.indexOf(record),
87247             node;
87248
87249         if (index > -1){
87250             node = me.bufferRender([record], index)[0];
87251             // ensure the node actually exists in the DOM
87252             if (me.getNode(record)) {
87253                 me.all.replaceElement(index, node, true);
87254                 me.updateIndexes(index, index);
87255                 // Maintain selection after update
87256                 // TODO: Move to approriate event handler.
87257                 me.selModel.refresh();
87258                 me.fireEvent('itemupdate', record, index, node);
87259             }
87260         }
87261
87262     },
87263
87264     // private
87265     onAdd : function(ds, records, index) {
87266         var me = this,
87267             nodes;
87268
87269         if (me.all.getCount() === 0) {
87270             me.refresh();
87271             return;
87272         }
87273
87274         nodes = me.bufferRender(records, index);
87275         me.doAdd(nodes, records, index);
87276
87277         me.selModel.refresh();
87278         me.updateIndexes(index);
87279         me.fireEvent('itemadd', records, index, nodes);
87280     },
87281
87282     doAdd: function(nodes, records, index) {
87283         var all = this.all;
87284
87285         if (index < all.getCount()) {
87286             all.item(index).insertSibling(nodes, 'before', true);
87287         } else {
87288             all.last().insertSibling(nodes, 'after', true);
87289         }
87290
87291         Ext.Array.insert(all.elements, index, nodes);
87292     },
87293
87294     // private
87295     onRemove : function(ds, record, index) {
87296         var me = this;
87297
87298         me.doRemove(record, index);
87299         me.updateIndexes(index);
87300         if (me.store.getCount() === 0){
87301             me.refresh();
87302         }
87303         me.fireEvent('itemremove', record, index);
87304     },
87305
87306     doRemove: function(record, index) {
87307         this.all.removeElement(index, true);
87308     },
87309
87310     /**
87311      * Refreshes an individual node's data from the store.
87312      * @param {Number} index The item's data index in the store
87313      */
87314     refreshNode : function(index){
87315         this.onUpdate(this.store, this.store.getAt(index));
87316     },
87317
87318     // private
87319     updateIndexes : function(startIndex, endIndex) {
87320         var ns = this.all.elements,
87321             records = this.store.getRange(),
87322             i;
87323             
87324         startIndex = startIndex || 0;
87325         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
87326         for(i = startIndex; i <= endIndex; i++){
87327             ns[i].viewIndex = i;
87328             ns[i].viewRecordId = records[i].internalId;
87329             if (!ns[i].boundView) {
87330                 ns[i].boundView = this.id;
87331             }
87332         }
87333     },
87334
87335     /**
87336      * Returns the store associated with this DataView.
87337      * @return {Ext.data.Store} The store
87338      */
87339     getStore : function(){
87340         return this.store;
87341     },
87342
87343     /**
87344      * Changes the data store bound to this view and refreshes it.
87345      * @param {Ext.data.Store} store The store to bind to this view
87346      */
87347     bindStore : function(store, initial) {
87348         var me = this,
87349             maskStore;
87350
87351         if (!initial && me.store) {
87352             if (store !== me.store && me.store.autoDestroy) {
87353                 me.store.destroyStore();
87354             }
87355             else {
87356                 me.mun(me.store, {
87357                     scope: me,
87358                     datachanged: me.onDataChanged,
87359                     add: me.onAdd,
87360                     remove: me.onRemove,
87361                     update: me.onUpdate,
87362                     clear: me.refresh
87363                 });
87364             }
87365             if (!store) {
87366                 // Ensure we have an instantiated LoadMask before we unbind it.
87367                 if (me.loadMask && me.loadMask.bindStore) {
87368                     me.loadMask.bindStore(null);
87369                 }
87370                 me.store = null;
87371             }
87372         }
87373         if (store) {
87374             store = Ext.data.StoreManager.lookup(store);
87375             me.mon(store, {
87376                 scope: me,
87377                 datachanged: me.onDataChanged,
87378                 add: me.onAdd,
87379                 remove: me.onRemove,
87380                 update: me.onUpdate,
87381                 clear: me.refresh
87382             });
87383             // Ensure we have an instantiated LoadMask before we bind it.
87384             if (me.loadMask && me.loadMask.bindStore) {
87385                 // View's store is a NodeStore, use owning TreePanel's Store
87386                 if (Ext.Array.contains(store.alias, 'store.node')) {
87387                     maskStore = this.ownerCt.store;
87388                 } else {
87389                     maskStore = store;
87390                 }
87391                 me.loadMask.bindStore(maskStore);
87392             }
87393         }
87394
87395         // Flag to say that initial refresh has not been performed.
87396         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
87397         me.viewReady = false;
87398
87399         me.store = store;
87400         // Bind the store to our selection model
87401         me.getSelectionModel().bind(store);
87402
87403         /*
87404          * This code used to have checks for:
87405          * if (store && (!initial || store.getCount() || me.emptyText)) {
87406          * Instead, just trigger a refresh and let the view itself figure out
87407          * what needs to happen. It can cause incorrect display if our store
87408          * has no data.
87409          */
87410         if (store) {
87411             if (initial && me.deferInitialRefresh) {
87412                 Ext.Function.defer(function () {
87413                     if (!me.isDestroyed) {
87414                         me.refresh(true);
87415                     }
87416                 }, 1);
87417             } else {
87418                 me.refresh(true);
87419             }
87420         }
87421     },
87422
87423     /**
87424      * @private
87425      * Calls this.refresh if this.blockRefresh is not true
87426      */
87427     onDataChanged: function() {
87428         if (this.blockRefresh !== true) {
87429             this.refresh.apply(this, arguments);
87430         }
87431     },
87432
87433     /**
87434      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
87435      * @param {HTMLElement} node
87436      * @return {HTMLElement} The template node
87437      */
87438     findItemByChild: function(node){
87439         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
87440     },
87441
87442     /**
87443      * Returns the template node by the Ext.EventObject or null if it is not found.
87444      * @param {Ext.EventObject} e
87445      */
87446     findTargetByEvent: function(e) {
87447         return e.getTarget(this.getItemSelector(), this.getTargetEl());
87448     },
87449
87450
87451     /**
87452      * Gets the currently selected nodes.
87453      * @return {HTMLElement[]} An array of HTMLElements
87454      */
87455     getSelectedNodes: function(){
87456         var nodes   = [],
87457             records = this.selModel.getSelection(),
87458             ln = records.length,
87459             i  = 0;
87460
87461         for (; i < ln; i++) {
87462             nodes.push(this.getNode(records[i]));
87463         }
87464
87465         return nodes;
87466     },
87467
87468     /**
87469      * Gets an array of the records from an array of nodes
87470      * @param {HTMLElement[]} nodes The nodes to evaluate
87471      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
87472      */
87473     getRecords: function(nodes) {
87474         var records = [],
87475             i = 0,
87476             len = nodes.length,
87477             data = this.store.data;
87478
87479         for (; i < len; i++) {
87480             records[records.length] = data.getByKey(nodes[i].viewRecordId);
87481         }
87482
87483         return records;
87484     },
87485
87486     /**
87487      * Gets a record from a node
87488      * @param {Ext.Element/HTMLElement} node The node to evaluate
87489      *
87490      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
87491      */
87492     getRecord: function(node){
87493         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
87494     },
87495
87496
87497     /**
87498      * Returns true if the passed node is selected, else false.
87499      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
87500      * @return {Boolean} True if selected, else false
87501      */
87502     isSelected : function(node) {
87503         // TODO: El/Idx/Record
87504         var r = this.getRecord(node);
87505         return this.selModel.isSelected(r);
87506     },
87507
87508     /**
87509      * Selects a record instance by record instance or index.
87510      * @param {Ext.data.Model[]/Number} records An array of records or an index
87511      * @param {Boolean} [keepExisting] True to keep existing selections
87512      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
87513      */
87514     select: function(records, keepExisting, suppressEvent) {
87515         this.selModel.select(records, keepExisting, suppressEvent);
87516     },
87517
87518     /**
87519      * Deselects a record instance by record instance or index.
87520      * @param {Ext.data.Model[]/Number} records An array of records or an index
87521      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
87522      */
87523     deselect: function(records, suppressEvent) {
87524         this.selModel.deselect(records, suppressEvent);
87525     },
87526
87527     /**
87528      * Gets a template node.
87529      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
87530      * the id of a template node or the record associated with the node.
87531      * @return {HTMLElement} The node or null if it wasn't found
87532      */
87533     getNode : function(nodeInfo) {
87534         if (!this.rendered) {
87535             return null;
87536         }
87537         if (Ext.isString(nodeInfo)) {
87538             return document.getElementById(nodeInfo);
87539         }
87540         if (Ext.isNumber(nodeInfo)) {
87541             return this.all.elements[nodeInfo];
87542         }
87543         if (nodeInfo instanceof Ext.data.Model) {
87544             return this.getNodeByRecord(nodeInfo);
87545         }
87546         return nodeInfo; // already an HTMLElement
87547     },
87548
87549     /**
87550      * @private
87551      */
87552     getNodeByRecord: function(record) {
87553         var ns = this.all.elements,
87554             ln = ns.length,
87555             i = 0;
87556
87557         for (; i < ln; i++) {
87558             if (ns[i].viewRecordId === record.internalId) {
87559                 return ns[i];
87560             }
87561         }
87562
87563         return null;
87564     },
87565
87566     /**
87567      * Gets a range nodes.
87568      * @param {Number} start (optional) The index of the first node in the range
87569      * @param {Number} end (optional) The index of the last node in the range
87570      * @return {HTMLElement[]} An array of nodes
87571      */
87572     getNodes: function(start, end) {
87573         var ns = this.all.elements,
87574             nodes = [],
87575             i;
87576
87577         start = start || 0;
87578         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
87579         if (start <= end) {
87580             for (i = start; i <= end && ns[i]; i++) {
87581                 nodes.push(ns[i]);
87582             }
87583         } else {
87584             for (i = start; i >= end && ns[i]; i--) {
87585                 nodes.push(ns[i]);
87586             }
87587         }
87588         return nodes;
87589     },
87590
87591     /**
87592      * Finds the index of the passed node.
87593      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
87594      * or a record associated with a node.
87595      * @return {Number} The index of the node or -1
87596      */
87597     indexOf: function(node) {
87598         node = this.getNode(node);
87599         if (Ext.isNumber(node.viewIndex)) {
87600             return node.viewIndex;
87601         }
87602         return this.all.indexOf(node);
87603     },
87604
87605     onDestroy : function() {
87606         var me = this;
87607
87608         me.all.clear();
87609         me.callParent();
87610         me.bindStore(null);
87611         me.selModel.destroy();
87612     },
87613
87614     // invoked by the selection model to maintain visual UI cues
87615     onItemSelect: function(record) {
87616         var node = this.getNode(record);
87617         
87618         if (node) {
87619             Ext.fly(node).addCls(this.selectedItemCls);
87620         }
87621     },
87622
87623     // invoked by the selection model to maintain visual UI cues
87624     onItemDeselect: function(record) {
87625         var node = this.getNode(record);
87626         
87627         if (node) {
87628             Ext.fly(node).removeCls(this.selectedItemCls);
87629         }
87630     },
87631
87632     getItemSelector: function() {
87633         return this.itemSelector;
87634     }
87635 }, function() {
87636     // all of this information is available directly
87637     // from the SelectionModel itself, the only added methods
87638     // to DataView regarding selection will perform some transformation/lookup
87639     // between HTMLElement/Nodes to records and vice versa.
87640     Ext.deprecate('extjs', '4.0', function() {
87641         Ext.view.AbstractView.override({
87642             /**
87643              * @cfg {Boolean} [multiSelect=false]
87644              * True to allow selection of more than one item at a time, false to allow selection of only a single item
87645              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
87646              */
87647             /**
87648              * @cfg {Boolean} [singleSelect=false]
87649              * True to allow selection of exactly one item at a time, false to allow no selection at all.
87650              * Note that if {@link #multiSelect} = true, this value will be ignored.
87651              */
87652             /**
87653              * @cfg {Boolean} [simpleSelect=false]
87654              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
87655              * false to force the user to hold Ctrl or Shift to select more than on item.
87656              */
87657
87658             /**
87659              * Gets the number of selected nodes.
87660              * @return {Number} The node count
87661              */
87662             getSelectionCount : function(){
87663                 if (Ext.global.console) {
87664                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
87665                 }
87666                 return this.selModel.getSelection().length;
87667             },
87668
87669             /**
87670              * Gets an array of the selected records
87671              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
87672              */
87673             getSelectedRecords : function(){
87674                 if (Ext.global.console) {
87675                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
87676                 }
87677                 return this.selModel.getSelection();
87678             },
87679
87680             select: function(records, keepExisting, supressEvents) {
87681                 if (Ext.global.console) {
87682                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
87683                 }
87684                 var sm = this.getSelectionModel();
87685                 return sm.select.apply(sm, arguments);
87686             },
87687
87688             clearSelections: function() {
87689                 if (Ext.global.console) {
87690                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
87691                 }
87692                 var sm = this.getSelectionModel();
87693                 return sm.deselectAll();
87694             }
87695         });
87696     });
87697 });
87698
87699 /**
87700  * @class Ext.Action
87701  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
87702  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
87703  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
87704  * and {@link Ext.menu.Menu} components).</p>
87705  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
87706  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
87707  * called at once through a single call to the Action.</p>
87708  * <p>Any Component that is to be configured with an Action must also support
87709  * the following methods:<ul>
87710  * <li><code>setText(string)</code></li>
87711  * <li><code>setIconCls(string)</code></li>
87712  * <li><code>setDisabled(boolean)</code></li>
87713  * <li><code>setVisible(boolean)</code></li>
87714  * <li><code>setHandler(function)</code></li></ul></p>
87715  * <p>This allows the Action to control its associated Components.</p>
87716  * Example usage:<br>
87717  * <pre><code>
87718 // Define the shared Action.  Each Component below will have the same
87719 // display text and icon, and will display the same message on click.
87720 var action = new Ext.Action({
87721     {@link #text}: 'Do something',
87722     {@link #handler}: function(){
87723         Ext.Msg.alert('Click', 'You did something.');
87724     },
87725     {@link #iconCls}: 'do-something',
87726     {@link #itemId}: 'myAction'
87727 });
87728
87729 var panel = new Ext.panel.Panel({
87730     title: 'Actions',
87731     width: 500,
87732     height: 300,
87733     tbar: [
87734         // Add the Action directly to a toolbar as a menu button
87735         action,
87736         {
87737             text: 'Action Menu',
87738             // Add the Action to a menu as a text item
87739             menu: [action]
87740         }
87741     ],
87742     items: [
87743         // Add the Action to the panel body as a standard button
87744         new Ext.button.Button(action)
87745     ],
87746     renderTo: Ext.getBody()
87747 });
87748
87749 // Change the text for all components using the Action
87750 action.setText('Something else');
87751
87752 // Reference an Action through a container using the itemId
87753 var btn = panel.getComponent('myAction');
87754 var aRef = btn.baseAction;
87755 aRef.setText('New text');
87756 </code></pre>
87757  */
87758 Ext.define('Ext.Action', {
87759
87760     /* Begin Definitions */
87761
87762     /* End Definitions */
87763
87764     /**
87765      * @cfg {String} [text='']
87766      * The text to set for all components configured by this Action.
87767      */
87768     /**
87769      * @cfg {String} [iconCls='']
87770      * The CSS class selector that specifies a background image to be used as the header icon for
87771      * all components configured by this Action.
87772      * <p>An example of specifying a custom icon class would be something like:
87773      * </p><pre><code>
87774 // specify the property in the config for the class:
87775      ...
87776      iconCls: 'do-something'
87777
87778 // css class that specifies background image to be used as the icon image:
87779 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
87780 </code></pre>
87781      */
87782     /**
87783      * @cfg {Boolean} [disabled=false]
87784      * True to disable all components configured by this Action, false to enable them.
87785      */
87786     /**
87787      * @cfg {Boolean} [hidden=false]
87788      * True to hide all components configured by this Action, false to show them.
87789      */
87790     /**
87791      * @cfg {Function} handler
87792      * The function that will be invoked by each component tied to this Action
87793      * when the component's primary event is triggered.
87794      */
87795     /**
87796      * @cfg {String} itemId
87797      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
87798      */
87799     /**
87800      * @cfg {Object} scope
87801      * The scope (this reference) in which the {@link #handler} is executed.
87802      * Defaults to the browser window.
87803      */
87804
87805     /**
87806      * Creates new Action.
87807      * @param {Object} config Config object.
87808      */
87809     constructor : function(config){
87810         this.initialConfig = config;
87811         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
87812         this.items = [];
87813     },
87814
87815     // private
87816     isAction : true,
87817
87818     /**
87819      * Sets the text to be displayed by all components configured by this Action.
87820      * @param {String} text The text to display
87821      */
87822     setText : function(text){
87823         this.initialConfig.text = text;
87824         this.callEach('setText', [text]);
87825     },
87826
87827     /**
87828      * Gets the text currently displayed by all components configured by this Action.
87829      */
87830     getText : function(){
87831         return this.initialConfig.text;
87832     },
87833
87834     /**
87835      * Sets the icon CSS class for all components configured by this Action.  The class should supply
87836      * a background image that will be used as the icon image.
87837      * @param {String} cls The CSS class supplying the icon image
87838      */
87839     setIconCls : function(cls){
87840         this.initialConfig.iconCls = cls;
87841         this.callEach('setIconCls', [cls]);
87842     },
87843
87844     /**
87845      * Gets the icon CSS class currently used by all components configured by this Action.
87846      */
87847     getIconCls : function(){
87848         return this.initialConfig.iconCls;
87849     },
87850
87851     /**
87852      * Sets the disabled state of all components configured by this Action.  Shortcut method
87853      * for {@link #enable} and {@link #disable}.
87854      * @param {Boolean} disabled True to disable the component, false to enable it
87855      */
87856     setDisabled : function(v){
87857         this.initialConfig.disabled = v;
87858         this.callEach('setDisabled', [v]);
87859     },
87860
87861     /**
87862      * Enables all components configured by this Action.
87863      */
87864     enable : function(){
87865         this.setDisabled(false);
87866     },
87867
87868     /**
87869      * Disables all components configured by this Action.
87870      */
87871     disable : function(){
87872         this.setDisabled(true);
87873     },
87874
87875     /**
87876      * Returns true if the components using this Action are currently disabled, else returns false.
87877      */
87878     isDisabled : function(){
87879         return this.initialConfig.disabled;
87880     },
87881
87882     /**
87883      * Sets the hidden state of all components configured by this Action.  Shortcut method
87884      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
87885      * @param {Boolean} hidden True to hide the component, false to show it
87886      */
87887     setHidden : function(v){
87888         this.initialConfig.hidden = v;
87889         this.callEach('setVisible', [!v]);
87890     },
87891
87892     /**
87893      * Shows all components configured by this Action.
87894      */
87895     show : function(){
87896         this.setHidden(false);
87897     },
87898
87899     /**
87900      * Hides all components configured by this Action.
87901      */
87902     hide : function(){
87903         this.setHidden(true);
87904     },
87905
87906     /**
87907      * Returns true if the components configured by this Action are currently hidden, else returns false.
87908      */
87909     isHidden : function(){
87910         return this.initialConfig.hidden;
87911     },
87912
87913     /**
87914      * Sets the function that will be called by each Component using this action when its primary event is triggered.
87915      * @param {Function} fn The function that will be invoked by the action's components.  The function
87916      * will be called with no arguments.
87917      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
87918      */
87919     setHandler : function(fn, scope){
87920         this.initialConfig.handler = fn;
87921         this.initialConfig.scope = scope;
87922         this.callEach('setHandler', [fn, scope]);
87923     },
87924
87925     /**
87926      * Executes the specified function once for each Component currently tied to this Action.  The function passed
87927      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
87928      * @param {Function} fn The function to execute for each component
87929      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
87930      */
87931     each : function(fn, scope){
87932         Ext.each(this.items, fn, scope);
87933     },
87934
87935     // private
87936     callEach : function(fnName, args){
87937         var items = this.items,
87938             i = 0,
87939             len = items.length;
87940
87941         for(; i < len; i++){
87942             items[i][fnName].apply(items[i], args);
87943         }
87944     },
87945
87946     // private
87947     addComponent : function(comp){
87948         this.items.push(comp);
87949         comp.on('destroy', this.removeComponent, this);
87950     },
87951
87952     // private
87953     removeComponent : function(comp){
87954         Ext.Array.remove(this.items, comp);
87955     },
87956
87957     /**
87958      * Executes this Action manually using the handler function specified in the original config object
87959      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
87960      * function will be passed on to the handler function.
87961      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
87962      */
87963     execute : function(){
87964         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
87965     }
87966 });
87967
87968 /**
87969  * Component layout for editors
87970  * @class Ext.layout.component.Editor
87971  * @extends Ext.layout.component.Component
87972  * @private
87973  */
87974 Ext.define('Ext.layout.component.Editor', {
87975
87976     /* Begin Definitions */
87977
87978     alias: ['layout.editor'],
87979
87980     extend: 'Ext.layout.component.Component',
87981
87982     /* End Definitions */
87983
87984     onLayout: function(width, height) {
87985         var me = this,
87986             owner = me.owner,
87987             autoSize = owner.autoSize;
87988             
87989         if (autoSize === true) {
87990             autoSize = {
87991                 width: 'field',
87992                 height: 'field'    
87993             };
87994         }
87995         
87996         if (autoSize) {
87997             width = me.getDimension(owner, autoSize.width, 'Width', width);
87998             height = me.getDimension(owner, autoSize.height, 'Height', height);
87999         }
88000         me.setTargetSize(width, height);
88001         owner.field.setSize(width, height);
88002     },
88003     
88004     getDimension: function(owner, type, dimension, actual){
88005         var method = 'get' + dimension;
88006         switch (type) {
88007             case 'boundEl':
88008                 return owner.boundEl[method]();
88009             case 'field':
88010                 return owner.field[method]();
88011             default:
88012                 return actual;
88013         }
88014     }
88015 });
88016 /**
88017  * @class Ext.Editor
88018  * @extends Ext.Component
88019  *
88020  * <p>
88021  * The Editor class is used to provide inline editing for elements on the page. The editor
88022  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
88023  * The editor is a floating Component, when the editor is shown it is automatically aligned to
88024  * display over the top of the bound element it is editing. The Editor contains several options
88025  * for how to handle key presses:
88026  * <ul>
88027  * <li>{@link #completeOnEnter}</li>
88028  * <li>{@link #cancelOnEsc}</li>
88029  * <li>{@link #swallowKeys}</li>
88030  * </ul>
88031  * It also has options for how to use the value once the editor has been activated:
88032  * <ul>
88033  * <li>{@link #revertInvalid}</li>
88034  * <li>{@link #ignoreNoChange}</li>
88035  * <li>{@link #updateEl}</li>
88036  * </ul>
88037  * Sample usage:
88038  * </p>
88039  * <pre><code>
88040 var editor = new Ext.Editor({
88041     updateEl: true, // update the innerHTML of the bound element when editing completes
88042     field: {
88043         xtype: 'textfield'
88044     }
88045 });
88046 var el = Ext.get('my-text'); // The element to 'edit'
88047 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
88048  * </code></pre>
88049  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
88050  *
88051  */
88052 Ext.define('Ext.Editor', {
88053
88054     /* Begin Definitions */
88055
88056     extend: 'Ext.Component',
88057
88058     alias: 'widget.editor',
88059
88060     requires: ['Ext.layout.component.Editor'],
88061
88062     /* End Definitions */
88063
88064    componentLayout: 'editor',
88065
88066     /**
88067     * @cfg {Ext.form.field.Field} field
88068     * The Field object (or descendant) or config object for field
88069     */
88070
88071     /**
88072      * @cfg {Boolean} allowBlur
88073      * True to {@link #completeEdit complete the editing process} if in edit mode when the
88074      * field is blurred.
88075      */
88076     allowBlur: true,
88077
88078     /**
88079      * @cfg {Boolean/Object} autoSize
88080      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
88081      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
88082      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
88083      * the editor object.
88084      * Examples:
88085      * <pre><code>
88086 autoSize: true // The editor will be sized to the height/width of the field
88087
88088 height: 21,
88089 autoSize: {
88090     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
88091 }
88092
88093 autoSize: {
88094     width: 'field', // Width from the field
88095     height: 'boundEl' // Height from the boundEl
88096 }
88097      * </pre></code>
88098      */
88099
88100     /**
88101      * @cfg {Boolean} revertInvalid
88102      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
88103      * validation fails
88104      */
88105     revertInvalid: true,
88106
88107     /**
88108      * @cfg {Boolean} [ignoreNoChange=false]
88109      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
88110      * the value has not changed.  Applies only to string values - edits for other data types
88111      * will never be ignored.
88112      */
88113
88114     /**
88115      * @cfg {Boolean} [hideEl=true]
88116      * False to keep the bound element visible while the editor is displayed
88117      */
88118
88119     /**
88120      * @cfg {Object} value
88121      * The data value of the underlying field
88122      */
88123     value : '',
88124
88125     /**
88126      * @cfg {String} alignment
88127      * The position to align to (see {@link Ext.Element#alignTo} for more details).
88128      */
88129     alignment: 'c-c?',
88130
88131     /**
88132      * @cfg {Number[]} offsets
88133      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
88134      */
88135     offsets: [0, 0],
88136
88137     /**
88138      * @cfg {Boolean/String} shadow
88139      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
88140      */
88141     shadow : 'frame',
88142
88143     /**
88144      * @cfg {Boolean} constrain
88145      * True to constrain the editor to the viewport
88146      */
88147     constrain : false,
88148
88149     /**
88150      * @cfg {Boolean} swallowKeys
88151      * Handle the keydown/keypress events so they don't propagate
88152      */
88153     swallowKeys : true,
88154
88155     /**
88156      * @cfg {Boolean} completeOnEnter
88157      * True to complete the edit when the enter key is pressed.
88158      */
88159     completeOnEnter : true,
88160
88161     /**
88162      * @cfg {Boolean} cancelOnEsc
88163      * True to cancel the edit when the escape key is pressed.
88164      */
88165     cancelOnEsc : true,
88166
88167     /**
88168      * @cfg {Boolean} updateEl
88169      * True to update the innerHTML of the bound element when the update completes
88170      */
88171     updateEl : false,
88172
88173     /**
88174      * @cfg {String/HTMLElement/Ext.Element} parentEl
88175      * An element to render to. Defaults to the <tt>document.body</tt>.
88176      */
88177
88178     // private overrides
88179     hidden: true,
88180     baseCls: Ext.baseCSSPrefix + 'editor',
88181
88182     initComponent : function() {
88183         var me = this,
88184             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
88185
88186         Ext.apply(field, {
88187             inEditor: true,
88188             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
88189         });
88190         me.mon(field, {
88191             scope: me,
88192             blur: {
88193                 fn: me.onBlur,
88194                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
88195                 delay: 1
88196             },
88197             specialkey: me.onSpecialKey
88198         });
88199
88200         if (field.grow) {
88201             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
88202         }
88203         me.floating = {
88204             constrain: me.constrain
88205         };
88206
88207         me.callParent(arguments);
88208
88209         me.addEvents(
88210             /**
88211              * @event beforestartedit
88212              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
88213              * false from the handler of this event.
88214              * @param {Ext.Editor} this
88215              * @param {Ext.Element} boundEl The underlying element bound to this editor
88216              * @param {Object} value The field value being set
88217              */
88218             'beforestartedit',
88219
88220             /**
88221              * @event startedit
88222              * Fires when this editor is displayed
88223              * @param {Ext.Editor} this
88224              * @param {Ext.Element} boundEl The underlying element bound to this editor
88225              * @param {Object} value The starting field value
88226              */
88227             'startedit',
88228
88229             /**
88230              * @event beforecomplete
88231              * Fires after a change has been made to the field, but before the change is reflected in the underlying
88232              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
88233              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
88234              * event will not fire since no edit actually occurred.
88235              * @param {Ext.Editor} this
88236              * @param {Object} value The current field value
88237              * @param {Object} startValue The original field value
88238              */
88239             'beforecomplete',
88240             /**
88241              * @event complete
88242              * Fires after editing is complete and any changed value has been written to the underlying field.
88243              * @param {Ext.Editor} this
88244              * @param {Object} value The current field value
88245              * @param {Object} startValue The original field value
88246              */
88247             'complete',
88248             /**
88249              * @event canceledit
88250              * Fires after editing has been canceled and the editor's value has been reset.
88251              * @param {Ext.Editor} this
88252              * @param {Object} value The user-entered field value that was discarded
88253              * @param {Object} startValue The original field value that was set back into the editor after cancel
88254              */
88255             'canceledit',
88256             /**
88257              * @event specialkey
88258              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
88259              * {@link Ext.EventObject#getKey} to determine which key was pressed.
88260              * @param {Ext.Editor} this
88261              * @param {Ext.form.field.Field} The field attached to this editor
88262              * @param {Ext.EventObject} event The event object
88263              */
88264             'specialkey'
88265         );
88266     },
88267
88268     // private
88269     onAutoSize: function(){
88270         this.doComponentLayout();
88271     },
88272
88273     // private
88274     onRender : function(ct, position) {
88275         var me = this,
88276             field = me.field,
88277             inputEl = field.inputEl;
88278
88279         me.callParent(arguments);
88280
88281         field.render(me.el);
88282         //field.hide();
88283         // Ensure the field doesn't get submitted as part of any form
88284         if (inputEl) {
88285             inputEl.dom.name = '';
88286             if (me.swallowKeys) {
88287                 inputEl.swallowEvent([
88288                     'keypress', // *** Opera
88289                     'keydown'   // *** all other browsers
88290                 ]);
88291             }
88292         }
88293     },
88294
88295     // private
88296     onSpecialKey : function(field, event) {
88297         var me = this,
88298             key = event.getKey(),
88299             complete = me.completeOnEnter && key == event.ENTER,
88300             cancel = me.cancelOnEsc && key == event.ESC;
88301
88302         if (complete || cancel) {
88303             event.stopEvent();
88304             // Must defer this slightly to prevent exiting edit mode before the field's own
88305             // key nav can handle the enter key, e.g. selecting an item in a combobox list
88306             Ext.defer(function() {
88307                 if (complete) {
88308                     me.completeEdit();
88309                 } else {
88310                     me.cancelEdit();
88311                 }
88312                 if (field.triggerBlur) {
88313                     field.triggerBlur();
88314                 }
88315             }, 10);
88316         }
88317
88318         this.fireEvent('specialkey', this, field, event);
88319     },
88320
88321     /**
88322      * Starts the editing process and shows the editor.
88323      * @param {String/HTMLElement/Ext.Element} el The element to edit
88324      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
88325       * to the innerHTML of el.
88326      */
88327     startEdit : function(el, value) {
88328         var me = this,
88329             field = me.field;
88330
88331         me.completeEdit();
88332         me.boundEl = Ext.get(el);
88333         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
88334
88335         if (!me.rendered) {
88336             me.render(me.parentEl || document.body);
88337         }
88338
88339         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
88340             me.startValue = value;
88341             me.show();
88342             field.reset();
88343             field.setValue(value);
88344             me.realign(true);
88345             field.focus(false, 10);
88346             if (field.autoSize) {
88347                 field.autoSize();
88348             }
88349             me.editing = true;
88350         }
88351     },
88352
88353     /**
88354      * Realigns the editor to the bound field based on the current alignment config value.
88355      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
88356      */
88357     realign : function(autoSize) {
88358         var me = this;
88359         if (autoSize === true) {
88360             me.doComponentLayout();
88361         }
88362         me.alignTo(me.boundEl, me.alignment, me.offsets);
88363     },
88364
88365     /**
88366      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
88367      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
88368      */
88369     completeEdit : function(remainVisible) {
88370         var me = this,
88371             field = me.field,
88372             value;
88373
88374         if (!me.editing) {
88375             return;
88376         }
88377
88378         // Assert combo values first
88379         if (field.assertValue) {
88380             field.assertValue();
88381         }
88382
88383         value = me.getValue();
88384         if (!field.isValid()) {
88385             if (me.revertInvalid !== false) {
88386                 me.cancelEdit(remainVisible);
88387             }
88388             return;
88389         }
88390
88391         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
88392             me.hideEdit(remainVisible);
88393             return;
88394         }
88395
88396         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
88397             // Grab the value again, may have changed in beforecomplete
88398             value = me.getValue();
88399             if (me.updateEl && me.boundEl) {
88400                 me.boundEl.update(value);
88401             }
88402             me.hideEdit(remainVisible);
88403             me.fireEvent('complete', me, value, me.startValue);
88404         }
88405     },
88406
88407     // private
88408     onShow : function() {
88409         var me = this;
88410
88411         me.callParent(arguments);
88412         if (me.hideEl !== false) {
88413             me.boundEl.hide();
88414         }
88415         me.fireEvent("startedit", me.boundEl, me.startValue);
88416     },
88417
88418     /**
88419      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
88420      * reverted to the original starting value.
88421      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
88422      */
88423     cancelEdit : function(remainVisible) {
88424         var me = this,
88425             startValue = me.startValue,
88426             value;
88427
88428         if (me.editing) {
88429             value = me.getValue();
88430             me.setValue(startValue);
88431             me.hideEdit(remainVisible);
88432             me.fireEvent('canceledit', me, value, startValue);
88433         }
88434     },
88435
88436     // private
88437     hideEdit: function(remainVisible) {
88438         if (remainVisible !== true) {
88439             this.editing = false;
88440             this.hide();
88441         }
88442     },
88443
88444     // private
88445     onBlur : function() {
88446         var me = this;
88447
88448         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
88449         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
88450             me.completeEdit();
88451         }
88452     },
88453
88454     // private
88455     onHide : function() {
88456         var me = this,
88457             field = me.field;
88458
88459         if (me.editing) {
88460             me.completeEdit();
88461             return;
88462         }
88463         field.blur();
88464         if (field.collapse) {
88465             field.collapse();
88466         }
88467
88468         //field.hide();
88469         if (me.hideEl !== false) {
88470             me.boundEl.show();
88471         }
88472         me.callParent(arguments);
88473     },
88474
88475     /**
88476      * Sets the data value of the editor
88477      * @param {Object} value Any valid value supported by the underlying field
88478      */
88479     setValue : function(value) {
88480         this.field.setValue(value);
88481     },
88482
88483     /**
88484      * Gets the data value of the editor
88485      * @return {Object} The data value
88486      */
88487     getValue : function() {
88488         return this.field.getValue();
88489     },
88490
88491     beforeDestroy : function() {
88492         var me = this;
88493
88494         Ext.destroy(me.field);
88495         delete me.field;
88496         delete me.parentEl;
88497         delete me.boundEl;
88498
88499         me.callParent(arguments);
88500     }
88501 });
88502 /**
88503  * @class Ext.Img
88504  * @extends Ext.Component
88505  *
88506  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
88507  * with the configured src.
88508  *
88509  * {@img Ext.Img/Ext.Img.png Ext.Img component}
88510  *
88511  * ## Example usage: 
88512  *
88513  *     var changingImage = Ext.create('Ext.Img', {
88514  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
88515  *         renderTo: Ext.getBody()
88516  *     });
88517  *      
88518  *     // change the src of the image programmatically
88519  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
88520 */
88521 Ext.define('Ext.Img', {
88522     extend: 'Ext.Component',
88523     alias: ['widget.image', 'widget.imagecomponent'],
88524     /** @cfg {String} src The image src */
88525     src: '',
88526
88527     getElConfig: function() {
88528         return {
88529             tag: 'img',
88530             src: this.src
88531         };
88532     },
88533     
88534     // null out this function, we can't set any html inside the image
88535     initRenderTpl: Ext.emptyFn,
88536     
88537     /**
88538      * Updates the {@link #src} of the image
88539      */
88540     setSrc: function(src) {
88541         var me = this,
88542             img = me.el;
88543         me.src = src;
88544         if (img) {
88545             img.dom.src = src;
88546         }
88547     }
88548 });
88549
88550 /**
88551  * @class Ext.Layer
88552  * @extends Ext.Element
88553  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
88554  * automatic maintaining of shadow/shim positions.
88555  *
88556  * @cfg {Boolean} [shim=true]
88557  * False to disable the iframe shim in browsers which need one.
88558  *
88559  * @cfg {String/Boolean} [shadow=false]
88560  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
88561  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
88562  *
88563  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
88564  * DomHelper object config to create element with.
88565  *
88566  * @cfg {Boolean} [constrain=true]
88567  * False to disable constrain to viewport.
88568  *
88569  * @cfg {String} cls
88570  * CSS class to add to the element
88571  *
88572  * @cfg {Number} [zindex=11000]
88573  * Starting z-index.
88574  *
88575  * @cfg {Number} [shadowOffset=4]
88576  * Number of pixels to offset the shadow
88577  *
88578  * @cfg {Boolean} [useDisplay=false]
88579  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
88580  * to use css style <tt>'display:none;'</tt> to hide the Layer.
88581  *
88582  * @cfg {String} visibilityCls
88583  * The CSS class name to add in order to hide this Layer if this layer
88584  * is configured with <code>{@link #hideMode}: 'asclass'</code>
88585  *
88586  * @cfg {String} hideMode
88587  * A String which specifies how this Layer will be hidden.
88588  * Values may be<div class="mdetail-params"><ul>
88589  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
88590  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
88591  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
88592  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
88593  * in a Component having zero dimensions.</li></ul></div>
88594  */
88595 Ext.define('Ext.Layer', {
88596     uses: ['Ext.Shadow'],
88597
88598     // shims are shared among layer to keep from having 100 iframes
88599     statics: {
88600         shims: []
88601     },
88602
88603     extend: 'Ext.Element',
88604
88605     /**
88606      * Creates new Layer.
88607      * @param {Object} config (optional) An object with config options.
88608      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
88609      * If the element is not found it creates it.
88610      */
88611     constructor: function(config, existingEl) {
88612         config = config || {};
88613         var me = this,
88614             dh = Ext.DomHelper,
88615             cp = config.parentEl,
88616             pel = cp ? Ext.getDom(cp) : document.body,
88617         hm = config.hideMode;
88618
88619         if (existingEl) {
88620             me.dom = Ext.getDom(existingEl);
88621         }
88622         if (!me.dom) {
88623             me.dom = dh.append(pel, config.dh || {
88624                 tag: 'div',
88625                 cls: Ext.baseCSSPrefix + 'layer'
88626             });
88627         } else {
88628             me.addCls(Ext.baseCSSPrefix + 'layer');
88629             if (!me.dom.parentNode) {
88630                 pel.appendChild(me.dom);
88631             }
88632         }
88633
88634         if (config.cls) {
88635             me.addCls(config.cls);
88636         }
88637         me.constrain = config.constrain !== false;
88638
88639         // Allow Components to pass their hide mode down to the Layer if they are floating.
88640         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
88641         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
88642         if (hm) {
88643             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
88644             if (me.visibilityMode == Ext.Element.ASCLASS) {
88645                 me.visibilityCls = config.visibilityCls;
88646             }
88647         } else if (config.useDisplay) {
88648             me.setVisibilityMode(Ext.Element.DISPLAY);
88649         } else {
88650             me.setVisibilityMode(Ext.Element.VISIBILITY);
88651         }
88652
88653         if (config.id) {
88654             me.id = me.dom.id = config.id;
88655         } else {
88656             me.id = Ext.id(me.dom);
88657         }
88658         me.position('absolute');
88659         if (config.shadow) {
88660             me.shadowOffset = config.shadowOffset || 4;
88661             me.shadow = Ext.create('Ext.Shadow', {
88662                 offset: me.shadowOffset,
88663                 mode: config.shadow
88664             });
88665             me.disableShadow();
88666         } else {
88667             me.shadowOffset = 0;
88668         }
88669         me.useShim = config.shim !== false && Ext.useShims;
88670         if (config.hidden === true) {
88671             me.hide();
88672         } else {
88673             me.show();
88674         }
88675     },
88676
88677     getZIndex: function() {
88678         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
88679     },
88680
88681     getShim: function() {
88682         var me = this,
88683             shim, pn;
88684
88685         if (!me.useShim) {
88686             return null;
88687         }
88688         if (!me.shim) {
88689             shim = me.self.shims.shift();
88690             if (!shim) {
88691                 shim = me.createShim();
88692                 shim.enableDisplayMode('block');
88693                 shim.hide();
88694             }
88695             pn = me.dom.parentNode;
88696             if (shim.dom.parentNode != pn) {
88697                 pn.insertBefore(shim.dom, me.dom);
88698             }
88699             me.shim = shim;
88700         }
88701         return me.shim;
88702     },
88703
88704     hideShim: function() {
88705         var me = this;
88706         
88707         if (me.shim) {
88708             me.shim.setDisplayed(false);
88709             me.self.shims.push(me.shim);
88710             delete me.shim;
88711         }
88712     },
88713
88714     disableShadow: function() {
88715         var me = this;
88716         
88717         if (me.shadow && !me.shadowDisabled) {
88718             me.shadowDisabled = true;
88719             me.shadow.hide();
88720             me.lastShadowOffset = me.shadowOffset;
88721             me.shadowOffset = 0;
88722         }
88723     },
88724
88725     enableShadow: function(show) {
88726         var me = this;
88727         
88728         if (me.shadow && me.shadowDisabled) {
88729             me.shadowDisabled = false;
88730             me.shadowOffset = me.lastShadowOffset;
88731             delete me.lastShadowOffset;
88732             if (show) {
88733                 me.sync(true);
88734             }
88735         }
88736     },
88737
88738     /**
88739      * @private
88740      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
88741      * <p>This code can execute repeatedly in milliseconds,
88742      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
88743      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
88744      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
88745      */
88746     sync: function(doShow) {
88747         var me = this,
88748             shadow = me.shadow,
88749             shadowPos, shimStyle, shadowSize;
88750
88751         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
88752             var shim = me.getShim(),
88753                 l = me.getLeft(true),
88754                 t = me.getTop(true),
88755                 w = me.dom.offsetWidth,
88756                 h = me.dom.offsetHeight,
88757                 shimIndex;
88758
88759             if (shadow && !me.shadowDisabled) {
88760                 if (doShow && !shadow.isVisible()) {
88761                     shadow.show(me);
88762                 } else {
88763                     shadow.realign(l, t, w, h);
88764                 }
88765                 if (shim) {
88766                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88767                     shimIndex = shim.getStyle('z-index');
88768                     if (shimIndex > me.zindex) {
88769                         me.shim.setStyle('z-index', me.zindex - 2);
88770                     }
88771                     shim.show();
88772                     // fit the shim behind the shadow, so it is shimmed too
88773                     if (shadow.isVisible()) {
88774                         shadowPos = shadow.el.getXY();
88775                         shimStyle = shim.dom.style;
88776                         shadowSize = shadow.el.getSize();
88777                         if (Ext.supports.CSS3BoxShadow) {
88778                             shadowSize.height += 6;
88779                             shadowSize.width += 4;
88780                             shadowPos[0] -= 2;
88781                             shadowPos[1] -= 4;
88782                         }
88783                         shimStyle.left = (shadowPos[0]) + 'px';
88784                         shimStyle.top = (shadowPos[1]) + 'px';
88785                         shimStyle.width = (shadowSize.width) + 'px';
88786                         shimStyle.height = (shadowSize.height) + 'px';
88787                     } else {
88788                         shim.setSize(w, h);
88789                         shim.setLeftTop(l, t);
88790                     }
88791                 }
88792             } else if (shim) {
88793                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88794                 shimIndex = shim.getStyle('z-index');
88795                 if (shimIndex > me.zindex) {
88796                     me.shim.setStyle('z-index', me.zindex - 2);
88797                 }
88798                 shim.show();
88799                 shim.setSize(w, h);
88800                 shim.setLeftTop(l, t);
88801             }
88802         }
88803         return me;
88804     },
88805
88806     remove: function() {
88807         this.hideUnders();
88808         this.callParent();
88809     },
88810
88811     // private
88812     beginUpdate: function() {
88813         this.updating = true;
88814     },
88815
88816     // private
88817     endUpdate: function() {
88818         this.updating = false;
88819         this.sync(true);
88820     },
88821
88822     // private
88823     hideUnders: function() {
88824         if (this.shadow) {
88825             this.shadow.hide();
88826         }
88827         this.hideShim();
88828     },
88829
88830     // private
88831     constrainXY: function() {
88832         if (this.constrain) {
88833             var vw = Ext.Element.getViewWidth(),
88834                 vh = Ext.Element.getViewHeight(),
88835                 s = Ext.getDoc().getScroll(),
88836                 xy = this.getXY(),
88837                 x = xy[0],
88838                 y = xy[1],
88839                 so = this.shadowOffset,
88840                 w = this.dom.offsetWidth + so,
88841                 h = this.dom.offsetHeight + so,
88842                 moved = false; // only move it if it needs it
88843             // first validate right/bottom
88844             if ((x + w) > vw + s.left) {
88845                 x = vw - w - so;
88846                 moved = true;
88847             }
88848             if ((y + h) > vh + s.top) {
88849                 y = vh - h - so;
88850                 moved = true;
88851             }
88852             // then make sure top/left isn't negative
88853             if (x < s.left) {
88854                 x = s.left;
88855                 moved = true;
88856             }
88857             if (y < s.top) {
88858                 y = s.top;
88859                 moved = true;
88860             }
88861             if (moved) {
88862                 Ext.Layer.superclass.setXY.call(this, [x, y]);
88863                 this.sync();
88864             }
88865         }
88866         return this;
88867     },
88868
88869     getConstrainOffset: function() {
88870         return this.shadowOffset;
88871     },
88872
88873     // overridden Element method
88874     setVisible: function(visible, animate, duration, callback, easing) {
88875         var me = this,
88876             cb;
88877
88878         // post operation processing
88879         cb = function() {
88880             if (visible) {
88881                 me.sync(true);
88882             }
88883             if (callback) {
88884                 callback();
88885             }
88886         };
88887
88888         // Hide shadow and shim if hiding
88889         if (!visible) {
88890             me.hideUnders(true);
88891         }
88892         me.callParent([visible, animate, duration, callback, easing]);
88893         if (!animate) {
88894             cb();
88895         }
88896         return me;
88897     },
88898
88899     // private
88900     beforeFx: function() {
88901         this.beforeAction();
88902         return this.callParent(arguments);
88903     },
88904
88905     // private
88906     afterFx: function() {
88907         this.callParent(arguments);
88908         this.sync(this.isVisible());
88909     },
88910
88911     // private
88912     beforeAction: function() {
88913         if (!this.updating && this.shadow) {
88914             this.shadow.hide();
88915         }
88916     },
88917
88918     // overridden Element method
88919     setLeft: function(left) {
88920         this.callParent(arguments);
88921         return this.sync();
88922     },
88923
88924     setTop: function(top) {
88925         this.callParent(arguments);
88926         return this.sync();
88927     },
88928
88929     setLeftTop: function(left, top) {
88930         this.callParent(arguments);
88931         return this.sync();
88932     },
88933
88934     setXY: function(xy, animate, duration, callback, easing) {
88935         var me = this;
88936         
88937         // Callback will restore shadow state and call the passed callback
88938         callback = me.createCB(callback);
88939
88940         me.fixDisplay();
88941         me.beforeAction();
88942         me.callParent([xy, animate, duration, callback, easing]);
88943         if (!animate) {
88944             callback();
88945         }
88946         return me;
88947     },
88948
88949     // private
88950     createCB: function(callback) {
88951         var me = this,
88952             showShadow = me.shadow && me.shadow.isVisible();
88953
88954         return function() {
88955             me.constrainXY();
88956             me.sync(showShadow);
88957             if (callback) {
88958                 callback();
88959             }
88960         };
88961     },
88962
88963     // overridden Element method
88964     setX: function(x, animate, duration, callback, easing) {
88965         this.setXY([x, this.getY()], animate, duration, callback, easing);
88966         return this;
88967     },
88968
88969     // overridden Element method
88970     setY: function(y, animate, duration, callback, easing) {
88971         this.setXY([this.getX(), y], animate, duration, callback, easing);
88972         return this;
88973     },
88974
88975     // overridden Element method
88976     setSize: function(w, h, animate, duration, callback, easing) {
88977         var me = this;
88978         
88979         // Callback will restore shadow state and call the passed callback
88980         callback = me.createCB(callback);
88981
88982         me.beforeAction();
88983         me.callParent([w, h, animate, duration, callback, easing]);
88984         if (!animate) {
88985             callback();
88986         }
88987         return me;
88988     },
88989
88990     // overridden Element method
88991     setWidth: function(w, animate, duration, callback, easing) {
88992         var me = this;
88993         
88994         // Callback will restore shadow state and call the passed callback
88995         callback = me.createCB(callback);
88996
88997         me.beforeAction();
88998         me.callParent([w, animate, duration, callback, easing]);
88999         if (!animate) {
89000             callback();
89001         }
89002         return me;
89003     },
89004
89005     // overridden Element method
89006     setHeight: function(h, animate, duration, callback, easing) {
89007         var me = this;
89008         
89009         // Callback will restore shadow state and call the passed callback
89010         callback = me.createCB(callback);
89011
89012         me.beforeAction();
89013         me.callParent([h, animate, duration, callback, easing]);
89014         if (!animate) {
89015             callback();
89016         }
89017         return me;
89018     },
89019
89020     // overridden Element method
89021     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
89022         var me = this;
89023         
89024         // Callback will restore shadow state and call the passed callback
89025         callback = me.createCB(callback);
89026
89027         me.beforeAction();
89028         if (!animate) {
89029             Ext.Layer.superclass.setXY.call(me, [x, y]);
89030             Ext.Layer.superclass.setSize.call(me, width, height);
89031             callback();
89032         } else {
89033             me.callParent([x, y, width, height, animate, duration, callback, easing]);
89034         }
89035         return me;
89036     },
89037
89038     /**
89039      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
89040      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
89041      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
89042      * element will receive the highest  z-index.
89043      * @param {Number} zindex The new z-index to set
89044      * @return {Ext.Layer} The Layer
89045      */
89046     setZIndex: function(zindex) {
89047         var me = this;
89048         
89049         me.zindex = zindex;
89050         if (me.getShim()) {
89051             me.shim.setStyle('z-index', zindex++);
89052         }
89053         if (me.shadow) {
89054             me.shadow.setZIndex(zindex++);
89055         }
89056         return me.setStyle('z-index', zindex);
89057     },
89058     
89059     setOpacity: function(opacity){
89060         if (this.shadow) {
89061             this.shadow.setOpacity(opacity);
89062         }
89063         return this.callParent(arguments);
89064     }
89065 });
89066
89067 /**
89068  * @class Ext.layout.component.ProgressBar
89069  * @extends Ext.layout.component.Component
89070  * @private
89071  */
89072
89073 Ext.define('Ext.layout.component.ProgressBar', {
89074
89075     /* Begin Definitions */
89076
89077     alias: ['layout.progressbar'],
89078
89079     extend: 'Ext.layout.component.Component',
89080
89081     /* End Definitions */
89082
89083     type: 'progressbar',
89084
89085     onLayout: function(width, height) {
89086         var me = this,
89087             owner = me.owner,
89088             textEl = owner.textEl;
89089         
89090         me.setElementSize(owner.el, width, height);
89091         textEl.setWidth(owner.el.getWidth(true));
89092         
89093         me.callParent([width, height]);
89094         
89095         owner.updateProgress(owner.value);
89096     }
89097 });
89098 /**
89099  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
89100  *
89101  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
89102  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
89103  * that has predictable points of interest at which you can update the control.
89104  *
89105  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
89106  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
89107  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
89108  * for indicating intermediate progress.
89109  *
89110  *     @example
89111  *     var p = Ext.create('Ext.ProgressBar', {
89112  *        renderTo: Ext.getBody(),
89113  *        width: 300
89114  *     });
89115  *
89116  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89117  *     p.wait({
89118  *         interval: 500, //bar will move fast!
89119  *         duration: 50000,
89120  *         increment: 15,
89121  *         text: 'Updating...',
89122  *         scope: this,
89123  *         fn: function(){
89124  *             p.updateText('Done!');
89125  *         }
89126  *     });
89127  */
89128 Ext.define('Ext.ProgressBar', {
89129     extend: 'Ext.Component',
89130     alias: 'widget.progressbar',
89131
89132     requires: [
89133         'Ext.Template',
89134         'Ext.CompositeElement',
89135         'Ext.TaskManager',
89136         'Ext.layout.component.ProgressBar'
89137     ],
89138
89139     uses: ['Ext.fx.Anim'],
89140
89141    /**
89142     * @cfg {Number} [value=0]
89143     * A floating point value between 0 and 1 (e.g., .5)
89144     */
89145
89146    /**
89147     * @cfg {String} [text='']
89148     * The progress bar text (defaults to '')
89149     */
89150
89151    /**
89152     * @cfg {String/HTMLElement/Ext.Element} textEl
89153     * The element to render the progress text to (defaults to the progress bar's internal text element)
89154     */
89155
89156    /**
89157     * @cfg {String} id
89158     * The progress bar element's id (defaults to an auto-generated id)
89159     */
89160
89161    /**
89162     * @cfg {String} [baseCls='x-progress']
89163     * The base CSS class to apply to the progress bar's wrapper element.
89164     */
89165     baseCls: Ext.baseCSSPrefix + 'progress',
89166
89167     config: {
89168         /**
89169         * @cfg {Boolean} animate
89170         * True to animate the progress bar during transitions
89171         */
89172         animate: false,
89173
89174         /**
89175          * @cfg {String} text
89176          * The text shown in the progress bar
89177          */
89178         text: ''
89179     },
89180
89181     // private
89182     waitTimer: null,
89183
89184     renderTpl: [
89185         '<div class="{baseCls}-text {baseCls}-text-back">',
89186             '<div>&#160;</div>',
89187         '</div>',
89188         '<div id="{id}-bar" class="{baseCls}-bar">',
89189             '<div class="{baseCls}-text">',
89190                 '<div>&#160;</div>',
89191             '</div>',
89192         '</div>'
89193     ],
89194
89195     componentLayout: 'progressbar',
89196
89197     // private
89198     initComponent: function() {
89199         this.callParent();
89200
89201         this.addChildEls('bar');
89202
89203         this.addEvents(
89204             /**
89205              * @event update
89206              * Fires after each update interval
89207              * @param {Ext.ProgressBar} this
89208              * @param {Number} value The current progress value
89209              * @param {String} text The current progress text
89210              */
89211             "update"
89212         );
89213     },
89214
89215     afterRender : function() {
89216         var me = this;
89217
89218         // This produces a composite w/2 el's (which is why we cannot use childEls or
89219         // renderSelectors):
89220         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
89221
89222         me.callParent(arguments);
89223
89224         if (me.value) {
89225             me.updateProgress(me.value, me.text);
89226         }
89227         else {
89228             me.updateText(me.text);
89229         }
89230     },
89231
89232     /**
89233      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
89234      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
89235      * it will never automatically reset -- you are responsible for determining when the progress is complete and
89236      * calling {@link #reset} to clear and/or hide the control.
89237      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
89238      * @param {String} [text=''] The string to display in the progress text element
89239      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
89240      * specified, the default for the class is used
89241      * @return {Ext.ProgressBar} this
89242      */
89243     updateProgress: function(value, text, animate) {
89244         var me = this,
89245             newWidth;
89246             
89247         me.value = value || 0;
89248         if (text) {
89249             me.updateText(text);
89250         }
89251         if (me.rendered && !me.isDestroyed) {
89252             if (me.isVisible(true)) {
89253                 newWidth = Math.floor(me.value * me.el.getWidth(true));
89254                 if (Ext.isForcedBorderBox) {
89255                     newWidth += me.bar.getBorderWidth("lr");
89256                 }
89257                 if (animate === true || (animate !== false && me.animate)) {
89258                     me.bar.stopAnimation();
89259                     me.bar.animate(Ext.apply({
89260                         to: {
89261                             width: newWidth + 'px'
89262                         }
89263                     }, me.animate));
89264                 } else {
89265                     me.bar.setWidth(newWidth);
89266                 }
89267             } else {
89268                 // force a layout when we're visible again
89269                 me.doComponentLayout();
89270             }
89271         }
89272         me.fireEvent('update', me, me.value, text);
89273         return me;
89274     },
89275
89276     /**
89277      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
89278      * display the updated text.
89279      * @param {String} [text=''] The string to display in the progress text element
89280      * @return {Ext.ProgressBar} this
89281      */
89282     updateText: function(text) {
89283         var me = this;
89284         
89285         me.text = text;
89286         if (me.rendered) {
89287             me.textEl.update(me.text);
89288         }
89289         return me;
89290     },
89291
89292     applyText : function(text) {
89293         this.updateText(text);
89294     },
89295
89296     /**
89297      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
89298      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
89299      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
89300      * {@link #reset}.
89301      *
89302      * Example usage:
89303      *
89304      *     var p = new Ext.ProgressBar({
89305      *        renderTo: 'my-el'
89306      *     });
89307      *
89308      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89309      *     var p = Ext.create('Ext.ProgressBar', {
89310      *        renderTo: Ext.getBody(),
89311      *        width: 300
89312      *     });
89313      *
89314      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89315      *     p.wait({
89316      *        interval: 500, //bar will move fast!
89317      *        duration: 50000,
89318      *        increment: 15,
89319      *        text: 'Updating...',
89320      *        scope: this,
89321      *        fn: function(){
89322      *           p.updateText('Done!');
89323      *        }
89324      *     });
89325      *
89326      *     //Or update indefinitely until some async action completes, then reset manually
89327      *     p.wait();
89328      *     myAction.on('complete', function(){
89329      *         p.reset();
89330      *         p.updateText('Done!');
89331      *     });
89332      *
89333      * @param {Object} config (optional) Configuration options
89334      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
89335      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
89336      * until reset is called)
89337      * @param {Number} config.interval The length of time in milliseconds between each progress update
89338      * (defaults to 1000 ms)
89339      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
89340      * value is not specified, the default for the class is used.
89341      * @param {Number} config.increment The number of progress update segments to display within the
89342      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
89343      * automatically wrap back to the beginning.
89344      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
89345      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
89346      * updating.  The function will be called with no arguments.  This function will be ignored if
89347      * duration is not specified since in that case the progress bar can only be stopped programmatically,
89348      * so any required function should be called by the same code after it resets the progress bar.
89349      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
89350      * duration and fn are both passed).
89351      * @return {Ext.ProgressBar} this
89352      */
89353     wait: function(o) {
89354         var me = this;
89355             
89356         if (!me.waitTimer) {
89357             scope = me;
89358             o = o || {};
89359             me.updateText(o.text);
89360             me.waitTimer = Ext.TaskManager.start({
89361                 run: function(i){
89362                     var inc = o.increment || 10;
89363                     i -= 1;
89364                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
89365                 },
89366                 interval: o.interval || 1000,
89367                 duration: o.duration,
89368                 onStop: function(){
89369                     if (o.fn) {
89370                         o.fn.apply(o.scope || me);
89371                     }
89372                     me.reset();
89373                 },
89374                 scope: scope
89375             });
89376         }
89377         return me;
89378     },
89379
89380     /**
89381      * Returns true if the progress bar is currently in a {@link #wait} operation
89382      * @return {Boolean} True if waiting, else false
89383      */
89384     isWaiting: function(){
89385         return this.waitTimer !== null;
89386     },
89387
89388     /**
89389      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
89390      * (using the {@link #hideMode} property internally).
89391      * @param {Boolean} [hide=false] True to hide the progress bar.
89392      * @return {Ext.ProgressBar} this
89393      */
89394     reset: function(hide){
89395         var me = this;
89396         
89397         me.updateProgress(0);
89398         me.clearTimer();
89399         if (hide === true) {
89400             me.hide();
89401         }
89402         return me;
89403     },
89404
89405     // private
89406     clearTimer: function(){
89407         var me = this;
89408         
89409         if (me.waitTimer) {
89410             me.waitTimer.onStop = null; //prevent recursion
89411             Ext.TaskManager.stop(me.waitTimer);
89412             me.waitTimer = null;
89413         }
89414     },
89415
89416     onDestroy: function(){
89417         var me = this;
89418         
89419         me.clearTimer();
89420         if (me.rendered) {
89421             if (me.textEl.isComposite) {
89422                 me.textEl.clear();
89423             }
89424             Ext.destroyMembers(me, 'textEl', 'progressBar');
89425         }
89426         me.callParent();
89427     }
89428 });
89429
89430 /**
89431  * Private utility class that manages the internal Shadow cache
89432  * @private
89433  */
89434 Ext.define('Ext.ShadowPool', {
89435     singleton: true,
89436     requires: ['Ext.DomHelper'],
89437
89438     markup: function() {
89439         if (Ext.supports.CSS3BoxShadow) {
89440             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
89441         } else if (Ext.isIE) {
89442             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
89443         } else {
89444             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
89445                 '<div class="xst" role="presentation">' +
89446                     '<div class="xstl" role="presentation"></div>' +
89447                     '<div class="xstc" role="presentation"></div>' +
89448                     '<div class="xstr" role="presentation"></div>' +
89449                 '</div>' +
89450                 '<div class="xsc" role="presentation">' +
89451                     '<div class="xsml" role="presentation"></div>' +
89452                     '<div class="xsmc" role="presentation"></div>' +
89453                     '<div class="xsmr" role="presentation"></div>' +
89454                 '</div>' +
89455                 '<div class="xsb" role="presentation">' +
89456                     '<div class="xsbl" role="presentation"></div>' +
89457                     '<div class="xsbc" role="presentation"></div>' +
89458                     '<div class="xsbr" role="presentation"></div>' +
89459                 '</div>' +
89460             '</div>';
89461         }
89462     }(),
89463
89464     shadows: [],
89465
89466     pull: function() {
89467         var sh = this.shadows.shift();
89468         if (!sh) {
89469             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
89470             sh.autoBoxAdjust = false;
89471         }
89472         return sh;
89473     },
89474
89475     push: function(sh) {
89476         this.shadows.push(sh);
89477     },
89478     
89479     reset: function() {
89480         Ext.Array.each(this.shadows, function(shadow) {
89481             shadow.remove();
89482         });
89483         this.shadows = [];
89484     }
89485 });
89486 /**
89487  * @class Ext.Shadow
89488  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
89489  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
89490  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
89491  */
89492 Ext.define('Ext.Shadow', {
89493     requires: ['Ext.ShadowPool'],
89494
89495     /**
89496      * Creates new Shadow.
89497      * @param {Object} config (optional) Config object.
89498      */
89499     constructor: function(config) {
89500         var me = this,
89501             adjusts = {
89502                 h: 0
89503             },
89504             offset,
89505             rad;
89506         
89507         Ext.apply(me, config);
89508         if (!Ext.isString(me.mode)) {
89509             me.mode = me.defaultMode;
89510         }
89511         offset = me.offset;
89512         rad = Math.floor(offset / 2);
89513         me.opacity = 50;
89514         switch (me.mode.toLowerCase()) {
89515             // all this hideous nonsense calculates the various offsets for shadows
89516             case "drop":
89517                 if (Ext.supports.CSS3BoxShadow) {
89518                     adjusts.w = adjusts.h = -offset;
89519                     adjusts.l = adjusts.t = offset;
89520                 } else {
89521                     adjusts.w = 0;
89522                     adjusts.l = adjusts.t = offset;
89523                     adjusts.t -= 1;
89524                     if (Ext.isIE) {
89525                         adjusts.l -= offset + rad;
89526                         adjusts.t -= offset + rad;
89527                         adjusts.w -= rad;
89528                         adjusts.h -= rad;
89529                         adjusts.t += 1;
89530                     }
89531                 }
89532                 break;
89533             case "sides":
89534                 if (Ext.supports.CSS3BoxShadow) {
89535                     adjusts.h -= offset;
89536                     adjusts.t = offset;
89537                     adjusts.l = adjusts.w = 0;
89538                 } else {
89539                     adjusts.w = (offset * 2);
89540                     adjusts.l = -offset;
89541                     adjusts.t = offset - 1;
89542                     if (Ext.isIE) {
89543                         adjusts.l -= (offset - rad);
89544                         adjusts.t -= offset + rad;
89545                         adjusts.l += 1;
89546                         adjusts.w -= (offset - rad) * 2;
89547                         adjusts.w -= rad + 1;
89548                         adjusts.h -= 1;
89549                     }
89550                 }
89551                 break;
89552             case "frame":
89553                 if (Ext.supports.CSS3BoxShadow) {
89554                     adjusts.l = adjusts.w = adjusts.t = 0;
89555                 } else {
89556                     adjusts.w = adjusts.h = (offset * 2);
89557                     adjusts.l = adjusts.t = -offset;
89558                     adjusts.t += 1;
89559                     adjusts.h -= 2;
89560                     if (Ext.isIE) {
89561                         adjusts.l -= (offset - rad);
89562                         adjusts.t -= (offset - rad);
89563                         adjusts.l += 1;
89564                         adjusts.w -= (offset + rad + 1);
89565                         adjusts.h -= (offset + rad);
89566                         adjusts.h += 1;
89567                     }
89568                     break;
89569                 }
89570         }
89571         me.adjusts = adjusts;
89572     },
89573
89574     /**
89575      * @cfg {String} mode
89576      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
89577      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
89578      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
89579      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
89580      * </ul></div>
89581      */
89582     /**
89583      * @cfg {Number} offset
89584      * The number of pixels to offset the shadow from the element
89585      */
89586     offset: 4,
89587
89588     // private
89589     defaultMode: "drop",
89590
89591     /**
89592      * Displays the shadow under the target element
89593      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
89594      */
89595     show: function(target) {
89596         var me = this,
89597             index;
89598         
89599         target = Ext.get(target);
89600         if (!me.el) {
89601             me.el = Ext.ShadowPool.pull();
89602             if (me.el.dom.nextSibling != target.dom) {
89603                 me.el.insertBefore(target);
89604             }
89605         }
89606         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
89607         me.el.setStyle("z-index", me.zIndex || index);
89608         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89609             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
89610         }
89611         me.realign(
89612             target.getLeft(true),
89613             target.getTop(true),
89614             target.dom.offsetWidth,
89615             target.dom.offsetHeight
89616         );
89617         me.el.dom.style.display = "block";
89618     },
89619
89620     /**
89621      * Returns true if the shadow is visible, else false
89622      */
89623     isVisible: function() {
89624         return this.el ? true: false;
89625     },
89626
89627     /**
89628      * Direct alignment when values are already available. Show must be called at least once before
89629      * calling this method to ensure it is initialized.
89630      * @param {Number} left The target element left position
89631      * @param {Number} top The target element top position
89632      * @param {Number} width The target element width
89633      * @param {Number} height The target element height
89634      */
89635     realign: function(l, t, targetWidth, targetHeight) {
89636         if (!this.el) {
89637             return;
89638         }
89639         var adjusts = this.adjusts,
89640             d = this.el.dom,
89641             targetStyle = d.style,
89642             shadowWidth,
89643             shadowHeight,
89644             cn,
89645             sww, 
89646             sws, 
89647             shs;
89648
89649         targetStyle.left = (l + adjusts.l) + "px";
89650         targetStyle.top = (t + adjusts.t) + "px";
89651         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
89652         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
89653         sws = shadowWidth + "px";
89654         shs = shadowHeight + "px";
89655         if (targetStyle.width != sws || targetStyle.height != shs) {
89656             targetStyle.width = sws;
89657             targetStyle.height = shs;
89658             if (Ext.supports.CSS3BoxShadow) {
89659                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
89660             } else {
89661
89662                 // Adjust the 9 point framed element to poke out on the required sides
89663                 if (!Ext.isIE) {
89664                     cn = d.childNodes;
89665                     sww = Math.max(0, (shadowWidth - 12)) + "px";
89666                     cn[0].childNodes[1].style.width = sww;
89667                     cn[1].childNodes[1].style.width = sww;
89668                     cn[2].childNodes[1].style.width = sww;
89669                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
89670                 }
89671             }
89672         }
89673     },
89674
89675     /**
89676      * Hides this shadow
89677      */
89678     hide: function() {
89679         var me = this;
89680         
89681         if (me.el) {
89682             me.el.dom.style.display = "none";
89683             Ext.ShadowPool.push(me.el);
89684             delete me.el;
89685         }
89686     },
89687
89688     /**
89689      * Adjust the z-index of this shadow
89690      * @param {Number} zindex The new z-index
89691      */
89692     setZIndex: function(z) {
89693         this.zIndex = z;
89694         if (this.el) {
89695             this.el.setStyle("z-index", z);
89696         }
89697     },
89698     
89699     /**
89700      * Sets the opacity of the shadow
89701      * @param {Number} opacity The opacity
89702      */
89703     setOpacity: function(opacity){
89704         if (this.el) {
89705             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89706                 opacity = Math.floor(opacity * 100 / 2) / 100;
89707             }
89708             this.opacity = opacity;
89709             this.el.setOpacity(opacity);
89710         }
89711     }
89712 });
89713 /**
89714  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
89715  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
89716  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
89717  *
89718  *     @example
89719  *     // display a dropdown menu:
89720  *     Ext.create('Ext.button.Split', {
89721  *         renderTo: Ext.getBody(),
89722  *         text: 'Options',
89723  *         // handle a click on the button itself
89724  *         handler: function() {
89725  *             alert("The button was clicked");
89726  *         },
89727  *         menu: new Ext.menu.Menu({
89728  *             items: [
89729  *                 // these will render as dropdown menu items when the arrow is clicked:
89730  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
89731  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
89732  *             ]
89733  *         })
89734  *     });
89735  *
89736  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
89737  * arrow is clicked:
89738  *
89739  *     Ext.create('Ext.button.Split', {
89740  *         renderTo: 'button-ct',
89741  *         text: 'Options',
89742  *         handler: optionsHandler,
89743  *         arrowHandler: myCustomHandler
89744  *     });
89745  *
89746  */
89747 Ext.define('Ext.button.Split', {
89748
89749     /* Begin Definitions */
89750     alias: 'widget.splitbutton',
89751
89752     extend: 'Ext.button.Button',
89753     alternateClassName: 'Ext.SplitButton',
89754     /* End Definitions */
89755     
89756     /**
89757      * @cfg {Function} arrowHandler
89758      * A function called when the arrow button is clicked (can be used instead of click event)
89759      */
89760     /**
89761      * @cfg {String} arrowTooltip
89762      * The title attribute of the arrow
89763      */
89764
89765     // private
89766     arrowCls      : 'split',
89767     split         : true,
89768
89769     // private
89770     initComponent : function(){
89771         this.callParent();
89772         /**
89773          * @event arrowclick
89774          * Fires when this button's arrow is clicked.
89775          * @param {Ext.button.Split} this
89776          * @param {Event} e The click event
89777          */
89778         this.addEvents("arrowclick");
89779     },
89780
89781     /**
89782      * Sets this button's arrow click handler.
89783      * @param {Function} handler The function to call when the arrow is clicked
89784      * @param {Object} scope (optional) Scope for the function passed above
89785      */
89786     setArrowHandler : function(handler, scope){
89787         this.arrowHandler = handler;
89788         this.scope = scope;
89789     },
89790
89791     // private
89792     onClick : function(e, t) {
89793         var me = this;
89794         
89795         e.preventDefault();
89796         if (!me.disabled) {
89797             if (me.overMenuTrigger) {
89798                 me.maybeShowMenu();
89799                 me.fireEvent("arrowclick", me, e);
89800                 if (me.arrowHandler) {
89801                     me.arrowHandler.call(me.scope || me, me, e);
89802                 }
89803             } else {
89804                 me.doToggle();
89805                 me.fireHandler();
89806             }
89807         }
89808     }
89809 });
89810 /**
89811  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
89812  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
89813  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
89814  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
89815  *
89816  *     @example
89817  *     Ext.create('Ext.button.Cycle', {
89818  *         showText: true,
89819  *         prependText: 'View as ',
89820  *         renderTo: Ext.getBody(),
89821  *         menu: {
89822  *             id: 'view-type-menu',
89823  *             items: [{
89824  *                 text: 'text only',
89825  *                 iconCls: 'view-text',
89826  *                 checked: true
89827  *             },{
89828  *                 text: 'HTML',
89829  *                 iconCls: 'view-html'
89830  *             }]
89831  *         },
89832  *         changeHandler: function(cycleBtn, activeItem) {
89833  *             Ext.Msg.alert('Change View', activeItem.text);
89834  *         }
89835  *     });
89836  */
89837 Ext.define('Ext.button.Cycle', {
89838
89839     /* Begin Definitions */
89840
89841     alias: 'widget.cycle',
89842
89843     extend: 'Ext.button.Split',
89844     alternateClassName: 'Ext.CycleButton',
89845
89846     /* End Definitions */
89847
89848     /**
89849      * @cfg {Object[]} items
89850      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
89851      * `{text:'Foo', iconCls:'foo-icon'}`)
89852      * 
89853      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
89854      * {@link Ext.menu.CheckItem CheckItems}.
89855      */
89856     /**
89857      * @cfg {Boolean} [showText=false]
89858      * True to display the active item's text as the button text. The Button will show its
89859      * configured {@link #text} if this config is omitted.
89860      */
89861     /**
89862      * @cfg {String} [prependText='']
89863      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
89864      * showText = true).
89865      */
89866     /**
89867      * @cfg {Function} changeHandler
89868      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
89869      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
89870      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
89871      * item)
89872      */
89873     /**
89874      * @cfg {String} forceIcon
89875      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
89876      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
89877      * button's icon to match the selected item's icon on change.
89878      */
89879     /**
89880      * @property {Ext.menu.Menu} menu
89881      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
89882      * available choices.
89883      */
89884
89885     // private
89886     getButtonText: function(item) {
89887         var me = this,
89888             text = '';
89889
89890         if (item && me.showText === true) {
89891             if (me.prependText) {
89892                 text += me.prependText;
89893             }
89894             text += item.text;
89895             return text;
89896         }
89897         return me.text;
89898     },
89899
89900     /**
89901      * Sets the button's active menu item.
89902      * @param {Ext.menu.CheckItem} item The item to activate
89903      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
89904      */
89905     setActiveItem: function(item, suppressEvent) {
89906         var me = this;
89907
89908         if (!Ext.isObject(item)) {
89909             item = me.menu.getComponent(item);
89910         }
89911         if (item) {
89912             if (!me.rendered) {
89913                 me.text = me.getButtonText(item);
89914                 me.iconCls = item.iconCls;
89915             } else {
89916                 me.setText(me.getButtonText(item));
89917                 me.setIconCls(item.iconCls);
89918             }
89919             me.activeItem = item;
89920             if (!item.checked) {
89921                 item.setChecked(true, false);
89922             }
89923             if (me.forceIcon) {
89924                 me.setIconCls(me.forceIcon);
89925             }
89926             if (!suppressEvent) {
89927                 me.fireEvent('change', me, item);
89928             }
89929         }
89930     },
89931
89932     /**
89933      * Gets the currently active menu item.
89934      * @return {Ext.menu.CheckItem} The active item
89935      */
89936     getActiveItem: function() {
89937         return this.activeItem;
89938     },
89939
89940     // private
89941     initComponent: function() {
89942         var me = this,
89943             checked = 0,
89944             items;
89945
89946         me.addEvents(
89947             /**
89948              * @event change
89949              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
89950              * set on this CycleButton, it will be called instead on active item change and this change event will not
89951              * be fired.
89952              * @param {Ext.button.Cycle} this
89953              * @param {Ext.menu.CheckItem} item The menu item that was selected
89954              */
89955             "change"
89956         );
89957
89958         if (me.changeHandler) {
89959             me.on('change', me.changeHandler, me.scope || me);
89960             delete me.changeHandler;
89961         }
89962
89963         // Allow them to specify a menu config which is a standard Button config.
89964         // Remove direct use of "items" in 5.0.
89965         items = (me.menu.items||[]).concat(me.items||[]);
89966         me.menu = Ext.applyIf({
89967             cls: Ext.baseCSSPrefix + 'cycle-menu',
89968             items: []
89969         }, me.menu);
89970
89971         // Convert all items to CheckItems
89972         Ext.each(items, function(item, i) {
89973             item = Ext.applyIf({
89974                 group: me.id,
89975                 itemIndex: i,
89976                 checkHandler: me.checkHandler,
89977                 scope: me,
89978                 checked: item.checked || false
89979             }, item);
89980             me.menu.items.push(item);
89981             if (item.checked) {
89982                 checked = i;
89983             }
89984         });
89985         me.itemCount = me.menu.items.length;
89986         me.callParent(arguments);
89987         me.on('click', me.toggleSelected, me);
89988         me.setActiveItem(checked, me);
89989
89990         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
89991         if (me.width && me.showText) {
89992             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
89993         }
89994     },
89995
89996     // private
89997     checkHandler: function(item, pressed) {
89998         if (pressed) {
89999             this.setActiveItem(item);
90000         }
90001     },
90002
90003     /**
90004      * This is normally called internally on button click, but can be called externally to advance the button's active
90005      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
90006      * item will be set to the first item in the menu.
90007      */
90008     toggleSelected: function() {
90009         var me = this,
90010             m = me.menu,
90011             checkItem;
90012
90013         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
90014         checkItem.setChecked(true);
90015     }
90016 });
90017 /**
90018  * Provides a container for arranging a group of related Buttons in a tabular manner.
90019  *
90020  *     @example
90021  *     Ext.create('Ext.panel.Panel', {
90022  *         title: 'Panel with ButtonGroup',
90023  *         width: 300,
90024  *         height:200,
90025  *         renderTo: document.body,
90026  *         bodyPadding: 10,
90027  *         html: 'HTML Panel Content',
90028  *         tbar: [{
90029  *             xtype: 'buttongroup',
90030  *             columns: 3,
90031  *             title: 'Clipboard',
90032  *             items: [{
90033  *                 text: 'Paste',
90034  *                 scale: 'large',
90035  *                 rowspan: 3,
90036  *                 iconCls: 'add',
90037  *                 iconAlign: 'top',
90038  *                 cls: 'btn-as-arrow'
90039  *             },{
90040  *                 xtype:'splitbutton',
90041  *                 text: 'Menu Button',
90042  *                 scale: 'large',
90043  *                 rowspan: 3,
90044  *                 iconCls: 'add',
90045  *                 iconAlign: 'top',
90046  *                 arrowAlign:'bottom',
90047  *                 menu: [{ text: 'Menu Item 1' }]
90048  *             },{
90049  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
90050  *             },{
90051  *                 text: 'Copy', iconCls: 'add16'
90052  *             },{
90053  *                 text: 'Format', iconCls: 'add16'
90054  *             }]
90055  *         }]
90056  *     });
90057  *
90058  */
90059 Ext.define('Ext.container.ButtonGroup', {
90060     extend: 'Ext.panel.Panel',
90061     alias: 'widget.buttongroup',
90062     alternateClassName: 'Ext.ButtonGroup',
90063
90064     /**
90065      * @cfg {Number} columns The `columns` configuration property passed to the
90066      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
90067      */
90068
90069     /**
90070      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
90071      */
90072     baseCls: Ext.baseCSSPrefix + 'btn-group',
90073
90074     /**
90075      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
90076      */
90077     layout: {
90078         type: 'table'
90079     },
90080
90081     defaultType: 'button',
90082
90083     /**
90084      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
90085      */
90086     frame: true,
90087
90088     frameHeader: false,
90089
90090     internalDefaults: {removeMode: 'container', hideParent: true},
90091
90092     initComponent : function(){
90093         // Copy the component's columns config to the layout if specified
90094         var me = this,
90095             cols = me.columns;
90096
90097         me.noTitleCls = me.baseCls + '-notitle';
90098         if (cols) {
90099             me.layout = Ext.apply({}, {columns: cols}, me.layout);
90100         }
90101
90102         if (!me.title) {
90103             me.addCls(me.noTitleCls);
90104         }
90105         me.callParent(arguments);
90106     },
90107
90108     afterLayout: function() {
90109         var me = this;
90110
90111         me.callParent(arguments);
90112
90113         // Pugly hack for a pugly browser:
90114         // If not an explicitly set width, then size the width to match the inner table
90115         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
90116             var t = me.getTargetEl();
90117             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
90118         }
90119     },
90120
90121     afterRender: function() {
90122         var me = this;
90123
90124         //we need to add an addition item in here so the ButtonGroup title is centered
90125         if (me.header) {
90126             // Header text cannot flex, but must be natural size if it's being centered
90127             delete me.header.items.items[0].flex;
90128
90129             // For Centering, surround the text with two flex:1 spacers.
90130             me.suspendLayout = true;
90131             me.header.insert(1, {
90132                 xtype: 'component',
90133                 ui   : me.ui,
90134                 flex : 1
90135             });
90136             me.header.insert(0, {
90137                 xtype: 'component',
90138                 ui   : me.ui,
90139                 flex : 1
90140             });
90141             me.suspendLayout = false;
90142         }
90143
90144         me.callParent(arguments);
90145     },
90146
90147     // private
90148     onBeforeAdd: function(component) {
90149         if (component.is('button')) {
90150             component.ui = component.ui + '-toolbar';
90151         }
90152         this.callParent(arguments);
90153     },
90154
90155     //private
90156     applyDefaults: function(c) {
90157         if (!Ext.isString(c)) {
90158             c = this.callParent(arguments);
90159             var d = this.internalDefaults;
90160             if (c.events) {
90161                 Ext.applyIf(c.initialConfig, d);
90162                 Ext.apply(c, d);
90163             } else {
90164                 Ext.applyIf(c, d);
90165             }
90166         }
90167         return c;
90168     }
90169
90170     /**
90171      * @cfg {Array} tools  @hide
90172      */
90173     /**
90174      * @cfg {Boolean} collapsible  @hide
90175      */
90176     /**
90177      * @cfg {Boolean} collapseMode  @hide
90178      */
90179     /**
90180      * @cfg {Boolean} animCollapse  @hide
90181      */
90182     /**
90183      * @cfg {Boolean} closable  @hide
90184      */
90185 });
90186
90187 /**
90188  * A specialized container representing the viewable application area (the browser viewport).
90189  *
90190  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
90191  * the browser viewport and manages window resizing. There may only be one Viewport created
90192  * in a page.
90193  *
90194  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
90195  * on its child Components if you configure it with a {@link #layout}.
90196  *
90197  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
90198  * required layout is simpler, a different layout should be chosen.
90199  *
90200  * For example, to simply make a single child item occupy all available space, use
90201  * {@link Ext.layout.container.Fit fit layout}.
90202  *
90203  * To display one "active" item at full size from a choice of several child items, use
90204  * {@link Ext.layout.container.Card card layout}.
90205  *
90206  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
90207  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
90208  * method of any of its child Panels may themselves have a layout.
90209  *
90210  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
90211  * for scrolling if needed using the {@link #autoScroll} config.
90212  *
90213  * An example showing a classic application border layout:
90214  *
90215  *     @example
90216  *     Ext.create('Ext.container.Viewport', {
90217  *         layout: 'border',
90218  *         items: [{
90219  *             region: 'north',
90220  *             html: '<h1 class="x-panel-header">Page Title</h1>',
90221  *             autoHeight: true,
90222  *             border: false,
90223  *             margins: '0 0 5 0'
90224  *         }, {
90225  *             region: 'west',
90226  *             collapsible: true,
90227  *             title: 'Navigation',
90228  *             width: 150
90229  *             // could use a TreePanel or AccordionLayout for navigational items
90230  *         }, {
90231  *             region: 'south',
90232  *             title: 'South Panel',
90233  *             collapsible: true,
90234  *             html: 'Information goes here',
90235  *             split: true,
90236  *             height: 100,
90237  *             minHeight: 100
90238  *         }, {
90239  *             region: 'east',
90240  *             title: 'East Panel',
90241  *             collapsible: true,
90242  *             split: true,
90243  *             width: 150
90244  *         }, {
90245  *             region: 'center',
90246  *             xtype: 'tabpanel', // TabPanel itself has no title
90247  *             activeTab: 0,      // First tab active by default
90248  *             items: {
90249  *                 title: 'Default Tab',
90250  *                 html: 'The first tab\'s content. Others may be added dynamically'
90251  *             }
90252  *         }]
90253  *     });
90254  */
90255 Ext.define('Ext.container.Viewport', {
90256     extend: 'Ext.container.Container',
90257     alias: 'widget.viewport',
90258     requires: ['Ext.EventManager'],
90259     alternateClassName: 'Ext.Viewport',
90260
90261     // Privatize config options which, if used, would interfere with the
90262     // correct operation of the Viewport as the sole manager of the
90263     // layout of the document body.
90264
90265     /**
90266      * @cfg {String/HTMLElement/Ext.Element} applyTo
90267      * Not applicable.
90268      */
90269
90270     /**
90271      * @cfg {Boolean} allowDomMove
90272      * Not applicable.
90273      */
90274
90275     /**
90276      * @cfg {Boolean} hideParent
90277      * Not applicable.
90278      */
90279
90280     /**
90281      * @cfg {String/HTMLElement/Ext.Element} renderTo
90282      * Not applicable. Always renders to document body.
90283      */
90284
90285     /**
90286      * @cfg {Boolean} hideParent
90287      * Not applicable.
90288      */
90289
90290     /**
90291      * @cfg {Number} height
90292      * Not applicable. Sets itself to viewport width.
90293      */
90294
90295     /**
90296      * @cfg {Number} width
90297      * Not applicable. Sets itself to viewport height.
90298      */
90299
90300     /**
90301      * @cfg {Boolean} autoHeight
90302      * Not applicable.
90303      */
90304
90305     /**
90306      * @cfg {Boolean} autoWidth
90307      * Not applicable.
90308      */
90309
90310     /**
90311      * @cfg {Boolean} deferHeight
90312      * Not applicable.
90313      */
90314
90315     /**
90316      * @cfg {Boolean} monitorResize
90317      * Not applicable.
90318      */
90319
90320     isViewport: true,
90321
90322     ariaRole: 'application',
90323
90324     initComponent : function() {
90325         var me = this,
90326             html = Ext.fly(document.body.parentNode),
90327             el;
90328         me.callParent(arguments);
90329         html.addCls(Ext.baseCSSPrefix + 'viewport');
90330         if (me.autoScroll) {
90331             html.setStyle('overflow', 'auto');
90332         }
90333         me.el = el = Ext.getBody();
90334         el.setHeight = Ext.emptyFn;
90335         el.setWidth = Ext.emptyFn;
90336         el.setSize = Ext.emptyFn;
90337         el.dom.scroll = 'no';
90338         me.allowDomMove = false;
90339         Ext.EventManager.onWindowResize(me.fireResize, me);
90340         me.renderTo = me.el;
90341         me.width = Ext.Element.getViewportWidth();
90342         me.height = Ext.Element.getViewportHeight();
90343     },
90344
90345     fireResize : function(w, h){
90346         // setSize is the single entry point to layouts
90347         this.setSize(w, h);
90348     }
90349 });
90350
90351 /*
90352  * This is a derivative of the similarly named class in the YUI Library.
90353  * The original license:
90354  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
90355  * Code licensed under the BSD License:
90356  * http://developer.yahoo.net/yui/license.txt
90357  */
90358
90359
90360 /**
90361  * @class Ext.dd.DDTarget
90362  * @extends Ext.dd.DragDrop
90363  * A DragDrop implementation that does not move, but can be a drop
90364  * target.  You would get the same result by simply omitting implementation
90365  * for the event callbacks, but this way we reduce the processing cost of the
90366  * event listener and the callbacks.
90367  */
90368 Ext.define('Ext.dd.DDTarget', {
90369     extend: 'Ext.dd.DragDrop',
90370
90371     /**
90372      * Creates new DDTarget.
90373      * @param {String} id the id of the element that is a drop target
90374      * @param {String} sGroup the group of related DragDrop objects
90375      * @param {Object} config an object containing configurable attributes.
90376      * Valid properties for DDTarget in addition to those in DragDrop: none.
90377      */
90378     constructor: function(id, sGroup, config) {
90379         if (id) {
90380             this.initTarget(id, sGroup, config);
90381         }
90382     },
90383
90384     /**
90385      * @hide
90386      * Overridden and disabled. A DDTarget does not support being dragged.
90387      * @method
90388      */
90389     getDragEl: Ext.emptyFn,
90390     /**
90391      * @hide
90392      * Overridden and disabled. A DDTarget does not support being dragged.
90393      * @method
90394      */
90395     isValidHandleChild: Ext.emptyFn,
90396     /**
90397      * @hide
90398      * Overridden and disabled. A DDTarget does not support being dragged.
90399      * @method
90400      */
90401     startDrag: Ext.emptyFn,
90402     /**
90403      * @hide
90404      * Overridden and disabled. A DDTarget does not support being dragged.
90405      * @method
90406      */
90407     endDrag: Ext.emptyFn,
90408     /**
90409      * @hide
90410      * Overridden and disabled. A DDTarget does not support being dragged.
90411      * @method
90412      */
90413     onDrag: Ext.emptyFn,
90414     /**
90415      * @hide
90416      * Overridden and disabled. A DDTarget does not support being dragged.
90417      * @method
90418      */
90419     onDragDrop: Ext.emptyFn,
90420     /**
90421      * @hide
90422      * Overridden and disabled. A DDTarget does not support being dragged.
90423      * @method
90424      */
90425     onDragEnter: Ext.emptyFn,
90426     /**
90427      * @hide
90428      * Overridden and disabled. A DDTarget does not support being dragged.
90429      * @method
90430      */
90431     onDragOut: Ext.emptyFn,
90432     /**
90433      * @hide
90434      * Overridden and disabled. A DDTarget does not support being dragged.
90435      * @method
90436      */
90437     onDragOver: Ext.emptyFn,
90438     /**
90439      * @hide
90440      * Overridden and disabled. A DDTarget does not support being dragged.
90441      * @method
90442      */
90443     onInvalidDrop: Ext.emptyFn,
90444     /**
90445      * @hide
90446      * Overridden and disabled. A DDTarget does not support being dragged.
90447      * @method
90448      */
90449     onMouseDown: Ext.emptyFn,
90450     /**
90451      * @hide
90452      * Overridden and disabled. A DDTarget does not support being dragged.
90453      * @method
90454      */
90455     onMouseUp: Ext.emptyFn,
90456     /**
90457      * @hide
90458      * Overridden and disabled. A DDTarget does not support being dragged.
90459      * @method
90460      */
90461     setXConstraint: Ext.emptyFn,
90462     /**
90463      * @hide
90464      * Overridden and disabled. A DDTarget does not support being dragged.
90465      * @method
90466      */
90467     setYConstraint: Ext.emptyFn,
90468     /**
90469      * @hide
90470      * Overridden and disabled. A DDTarget does not support being dragged.
90471      * @method
90472      */
90473     resetConstraints: Ext.emptyFn,
90474     /**
90475      * @hide
90476      * Overridden and disabled. A DDTarget does not support being dragged.
90477      * @method
90478      */
90479     clearConstraints: Ext.emptyFn,
90480     /**
90481      * @hide
90482      * Overridden and disabled. A DDTarget does not support being dragged.
90483      * @method
90484      */
90485     clearTicks: Ext.emptyFn,
90486     /**
90487      * @hide
90488      * Overridden and disabled. A DDTarget does not support being dragged.
90489      * @method
90490      */
90491     setInitPosition: Ext.emptyFn,
90492     /**
90493      * @hide
90494      * Overridden and disabled. A DDTarget does not support being dragged.
90495      * @method
90496      */
90497     setDragElId: Ext.emptyFn,
90498     /**
90499      * @hide
90500      * Overridden and disabled. A DDTarget does not support being dragged.
90501      * @method
90502      */
90503     setHandleElId: Ext.emptyFn,
90504     /**
90505      * @hide
90506      * Overridden and disabled. A DDTarget does not support being dragged.
90507      * @method
90508      */
90509     setOuterHandleElId: Ext.emptyFn,
90510     /**
90511      * @hide
90512      * Overridden and disabled. A DDTarget does not support being dragged.
90513      * @method
90514      */
90515     addInvalidHandleClass: Ext.emptyFn,
90516     /**
90517      * @hide
90518      * Overridden and disabled. A DDTarget does not support being dragged.
90519      * @method
90520      */
90521     addInvalidHandleId: Ext.emptyFn,
90522     /**
90523      * @hide
90524      * Overridden and disabled. A DDTarget does not support being dragged.
90525      * @method
90526      */
90527     addInvalidHandleType: Ext.emptyFn,
90528     /**
90529      * @hide
90530      * Overridden and disabled. A DDTarget does not support being dragged.
90531      * @method
90532      */
90533     removeInvalidHandleClass: Ext.emptyFn,
90534     /**
90535      * @hide
90536      * Overridden and disabled. A DDTarget does not support being dragged.
90537      * @method
90538      */
90539     removeInvalidHandleId: Ext.emptyFn,
90540     /**
90541      * @hide
90542      * Overridden and disabled. A DDTarget does not support being dragged.
90543      * @method
90544      */
90545     removeInvalidHandleType: Ext.emptyFn,
90546
90547     toString: function() {
90548         return ("DDTarget " + this.id);
90549     }
90550 });
90551 /**
90552  * @class Ext.dd.DragTracker
90553  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
90554  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
90555  * an element that can be dragged around to change the Slider's value.
90556  * DragTracker provides a series of template methods that should be overridden to provide functionality
90557  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
90558  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
90559  */
90560 Ext.define('Ext.dd.DragTracker', {
90561
90562     uses: ['Ext.util.Region'],
90563
90564     mixins: {
90565         observable: 'Ext.util.Observable'
90566     },
90567
90568     /**
90569      * @property {Boolean} active
90570      * Read-only property indicated whether the user is currently dragging this
90571      * tracker.
90572      */
90573     active: false,
90574
90575     /**
90576      * @property {HTMLElement} dragTarget
90577      * <p><b>Only valid during drag operations. Read-only.</b></p>
90578      * <p>The element being dragged.</p>
90579      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
90580      */
90581
90582     /**
90583      * @cfg {Boolean} trackOver
90584      * <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>
90585      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
90586      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90587      */
90588     trackOver: false,
90589
90590     /**
90591      * @cfg {String} overCls
90592      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
90593      * when a delegate element) is mouseovered.</p>
90594      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90595      */
90596
90597     /**
90598      * @cfg {Ext.util.Region/Ext.Element} constrainTo
90599      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
90600      * the result of the {@link #getOffset} call.</p>
90601      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
90602      */
90603
90604     /**
90605      * @cfg {Number} tolerance
90606      * Number of pixels the drag target must be moved before dragging is
90607      * considered to have started. Defaults to <code>5</code>.
90608      */
90609     tolerance: 5,
90610
90611     /**
90612      * @cfg {Boolean/Number} autoStart
90613      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
90614      * Specify a Number for the number of milliseconds to defer trigger start.
90615      */
90616     autoStart: false,
90617
90618     /**
90619      * @cfg {String} delegate
90620      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
90621      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
90622      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
90623      */
90624
90625     /**
90626      * @cfg {Boolean} preventDefault
90627      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
90628      */
90629
90630     /**
90631      * @cfg {Boolean} stopEvent
90632      * 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>.
90633      */
90634
90635     constructor : function(config){
90636         Ext.apply(this, config);
90637         this.addEvents(
90638             /**
90639              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90640              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
90641              * used, when the mouse enters a delegate element).</p>
90642              * @param {Object} this
90643              * @param {Object} e event object
90644              * @param {HTMLElement} target The element mouseovered.
90645              */
90646             'mouseover',
90647
90648             /**
90649              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90650              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
90651              * used, when the mouse exits a delegate element).</p>
90652              * @param {Object} this
90653              * @param {Object} e event object
90654              */
90655             'mouseout',
90656
90657             /**
90658              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
90659              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
90660              * the {@link #autoStart} timer fires.</p>
90661              * <p>Return false to veto the drag operation.</p>
90662              * @param {Object} this
90663              * @param {Object} e event object
90664              */
90665             'mousedown',
90666
90667             /**
90668              * @event mouseup
90669              * @param {Object} this
90670              * @param {Object} e event object
90671              */
90672             'mouseup',
90673
90674             /**
90675              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
90676              * @param {Object} this
90677              * @param {Object} e event object
90678              */
90679             'mousemove',
90680
90681             /**
90682              * @event beforestart
90683              * @param {Object} this
90684              * @param {Object} e event object
90685              */
90686             'beforedragstart',
90687
90688             /**
90689              * @event dragstart
90690              * @param {Object} this
90691              * @param {Object} e event object
90692              */
90693             'dragstart',
90694
90695             /**
90696              * @event dragend
90697              * @param {Object} this
90698              * @param {Object} e event object
90699              */
90700             'dragend',
90701
90702             /**
90703              * @event drag
90704              * @param {Object} this
90705              * @param {Object} e event object
90706              */
90707             'drag'
90708         );
90709
90710         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
90711
90712         if (this.el) {
90713             this.initEl(this.el);
90714         }
90715
90716         // Dont pass the config so that it is not applied to 'this' again
90717         this.mixins.observable.constructor.call(this);
90718         if (this.disabled) {
90719             this.disable();
90720         }
90721
90722     },
90723
90724     /**
90725      * Initializes the DragTracker on a given element.
90726      * @param {Ext.Element/HTMLElement} el The element
90727      */
90728     initEl: function(el) {
90729         this.el = Ext.get(el);
90730
90731         // The delegate option may also be an element on which to listen
90732         this.handle = Ext.get(this.delegate);
90733
90734         // If delegate specified an actual element to listen on, we do not use the delegate listener option
90735         this.delegate = this.handle ? undefined : this.delegate;
90736
90737         if (!this.handle) {
90738             this.handle = this.el;
90739         }
90740
90741         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
90742         // We process mousedown to begin tracking.
90743         this.mon(this.handle, {
90744             mousedown: this.onMouseDown,
90745             delegate: this.delegate,
90746             scope: this
90747         });
90748
90749         // If configured to do so, track mouse entry and exit into the target (or delegate).
90750         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
90751         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
90752         if (this.trackOver || this.overCls) {
90753             this.mon(this.handle, {
90754                 mouseover: this.onMouseOver,
90755                 mouseout: this.onMouseOut,
90756                 delegate: this.delegate,
90757                 scope: this
90758             });
90759         }
90760     },
90761
90762     disable: function() {
90763         this.disabled = true;
90764     },
90765
90766     enable: function() {
90767         this.disabled = false;
90768     },
90769
90770     destroy : function() {
90771         this.clearListeners();
90772         delete this.el;
90773     },
90774
90775     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
90776     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
90777     onMouseOver: function(e, target) {
90778         var me = this;
90779         if (!me.disabled) {
90780             if (Ext.EventManager.contains(e) || me.delegate) {
90781                 me.mouseIsOut = false;
90782                 if (me.overCls) {
90783                     me.el.addCls(me.overCls);
90784                 }
90785                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
90786             }
90787         }
90788     },
90789
90790     // When the pointer exits a tracking element, fire a mouseout.
90791     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
90792     onMouseOut: function(e) {
90793         if (this.mouseIsDown) {
90794             this.mouseIsOut = true;
90795         } else {
90796             if (this.overCls) {
90797                 this.el.removeCls(this.overCls);
90798             }
90799             this.fireEvent('mouseout', this, e);
90800         }
90801     },
90802
90803     onMouseDown: function(e, target){
90804         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
90805         if (this.disabled ||e.dragTracked) {
90806             return;
90807         }
90808
90809         // This information should be available in mousedown listener and onBeforeStart implementations
90810         this.dragTarget = this.delegate ? target : this.handle.dom;
90811         this.startXY = this.lastXY = e.getXY();
90812         this.startRegion = Ext.fly(this.dragTarget).getRegion();
90813
90814         if (this.fireEvent('mousedown', this, e) === false ||
90815             this.fireEvent('beforedragstart', this, e) === false ||
90816             this.onBeforeStart(e) === false) {
90817             return;
90818         }
90819
90820         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
90821         // The onMouseOut method will only ever be called after mouseup.
90822         this.mouseIsDown = true;
90823
90824         // Flag for downstream DragTracker instances that the mouse is being tracked.
90825         e.dragTracked = true;
90826
90827         if (this.preventDefault !== false) {
90828             e.preventDefault();
90829         }
90830         Ext.getDoc().on({
90831             scope: this,
90832             mouseup: this.onMouseUp,
90833             mousemove: this.onMouseMove,
90834             selectstart: this.stopSelect
90835         });
90836         if (this.autoStart) {
90837             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
90838         }
90839     },
90840
90841     onMouseMove: function(e, target){
90842         // BrowserBug: IE hack to see if button was released outside of window.
90843         // Needed in IE6-9 in quirks and strictmode
90844         if (this.active && Ext.isIE && !e.browserEvent.button) {
90845             e.preventDefault();
90846             this.onMouseUp(e);
90847             return;
90848         }
90849
90850         e.preventDefault();
90851         var xy = e.getXY(),
90852             s = this.startXY;
90853
90854         this.lastXY = xy;
90855         if (!this.active) {
90856             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
90857                 this.triggerStart(e);
90858             } else {
90859                 return;
90860             }
90861         }
90862
90863         // Returning false from a mousemove listener deactivates
90864         if (this.fireEvent('mousemove', this, e) === false) {
90865             this.onMouseUp(e);
90866         } else {
90867             this.onDrag(e);
90868             this.fireEvent('drag', this, e);
90869         }
90870     },
90871
90872     onMouseUp: function(e) {
90873         // Clear the flag which ensures onMouseOut fires only after the mouse button
90874         // is lifted if the mouseout happens *during* a drag.
90875         this.mouseIsDown = false;
90876
90877         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
90878         if (this.mouseIsOut) {
90879             this.mouseIsOut = false;
90880             this.onMouseOut(e);
90881         }
90882         e.preventDefault();
90883         this.fireEvent('mouseup', this, e);
90884         this.endDrag(e);
90885     },
90886
90887     /**
90888      * @private
90889      * Stop the drag operation, and remove active mouse listeners.
90890      */
90891     endDrag: function(e) {
90892         var doc = Ext.getDoc(),
90893         wasActive = this.active;
90894
90895         doc.un('mousemove', this.onMouseMove, this);
90896         doc.un('mouseup', this.onMouseUp, this);
90897         doc.un('selectstart', this.stopSelect, this);
90898         this.clearStart();
90899         this.active = false;
90900         if (wasActive) {
90901             this.onEnd(e);
90902             this.fireEvent('dragend', this, e);
90903         }
90904         // Private property calculated when first required and only cached during a drag
90905         delete this._constrainRegion;
90906
90907         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
90908         delete Ext.EventObject.dragTracked;
90909     },
90910
90911     triggerStart: function(e) {
90912         this.clearStart();
90913         this.active = true;
90914         this.onStart(e);
90915         this.fireEvent('dragstart', this, e);
90916     },
90917
90918     clearStart : function() {
90919         if (this.timer) {
90920             clearTimeout(this.timer);
90921             delete this.timer;
90922         }
90923     },
90924
90925     stopSelect : function(e) {
90926         e.stopEvent();
90927         return false;
90928     },
90929
90930     /**
90931      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
90932      * holds the mouse button down. Return false to disallow the drag
90933      * @param {Ext.EventObject} e The event object
90934      * @template
90935      */
90936     onBeforeStart : function(e) {
90937
90938     },
90939
90940     /**
90941      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
90942      * (e.g. the user has moved the tracked element beyond the specified tolerance)
90943      * @param {Ext.EventObject} e The event object
90944      * @template
90945      */
90946     onStart : function(xy) {
90947
90948     },
90949
90950     /**
90951      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
90952      * @param {Ext.EventObject} e The event object
90953      * @template
90954      */
90955     onDrag : function(e) {
90956
90957     },
90958
90959     /**
90960      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
90961      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
90962      * @param {Ext.EventObject} e The event object
90963      * @template
90964      */
90965     onEnd : function(e) {
90966
90967     },
90968
90969     /**
90970      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
90971      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
90972      * {@link #delegate} selector.</p>
90973      * @return {Ext.Element} The element currently being tracked.
90974      */
90975     getDragTarget : function(){
90976         return this.dragTarget;
90977     },
90978
90979     /**
90980      * @private
90981      * @returns {Ext.Element} The DragTracker's encapsulating element.
90982      */
90983     getDragCt : function(){
90984         return this.el;
90985     },
90986
90987     /**
90988      * @private
90989      * Return the Region into which the drag operation is constrained.
90990      * Either the XY pointer itself can be constrained, or the dragTarget element
90991      * The private property _constrainRegion is cached until onMouseUp
90992      */
90993     getConstrainRegion: function() {
90994         if (this.constrainTo) {
90995             if (this.constrainTo instanceof Ext.util.Region) {
90996                 return this.constrainTo;
90997             }
90998             if (!this._constrainRegion) {
90999                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
91000             }
91001         } else {
91002             if (!this._constrainRegion) {
91003                 this._constrainRegion = this.getDragCt().getViewRegion();
91004             }
91005         }
91006         return this._constrainRegion;
91007     },
91008
91009     getXY : function(constrain){
91010         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
91011     },
91012
91013     /**
91014      * Returns the X, Y offset of the current mouse position from the mousedown point.
91015      *
91016      * This method may optionally constrain the real offset values, and returns a point coerced in one
91017      * of two modes:
91018      *
91019      *  - `point`
91020      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
91021      *  - `dragTarget`
91022      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
91023      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
91024      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
91025      *
91026      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
91027      * as `point` or `dragTarget`. See above.
91028      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
91029      */
91030     getOffset : function(constrain){
91031         var xy = this.getXY(constrain),
91032             s = this.startXY;
91033
91034         return [xy[0]-s[0], xy[1]-s[1]];
91035     },
91036
91037     constrainModes: {
91038         // Constrain the passed point to within the constrain region
91039         point: function(me, xy) {
91040             var dr = me.dragRegion,
91041                 constrainTo = me.getConstrainRegion();
91042
91043             // No constraint
91044             if (!constrainTo) {
91045                 return xy;
91046             }
91047
91048             dr.x = dr.left = dr[0] = dr.right = xy[0];
91049             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
91050             dr.constrainTo(constrainTo);
91051
91052             return [dr.left, dr.top];
91053         },
91054
91055         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
91056         dragTarget: function(me, xy) {
91057             var s = me.startXY,
91058                 dr = me.startRegion.copy(),
91059                 constrainTo = me.getConstrainRegion(),
91060                 adjust;
91061
91062             // No constraint
91063             if (!constrainTo) {
91064                 return xy;
91065             }
91066
91067             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
91068             // If it overflows, we constrain the passed XY to bring the potential
91069             // region back within the boundary.
91070             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
91071
91072             // Constrain the X coordinate by however much the dragTarget overflows
91073             if (dr.right > constrainTo.right) {
91074                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
91075                 dr.left += adjust;
91076             }
91077             if (dr.left < constrainTo.left) {
91078                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
91079             }
91080
91081             // Constrain the Y coordinate by however much the dragTarget overflows
91082             if (dr.bottom > constrainTo.bottom) {
91083                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
91084                 dr.top += adjust;
91085             }
91086             if (dr.top < constrainTo.top) {
91087                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
91088             }
91089             return xy;
91090         }
91091     }
91092 });
91093 /**
91094  * @class Ext.dd.DragZone
91095  * @extends Ext.dd.DragSource
91096  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
91097  * <p>This class does not move the drag target nodes, but a proxy element which may contain
91098  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
91099  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
91100  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
91101  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
91102  * is the most efficient way to "activate" those nodes.</p>
91103  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
91104  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
91105  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
91106  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
91107  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91108  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
91109  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
91110 myDataView.on('render', function(v) {
91111     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
91112
91113 //      On receipt of a mousedown event, see if it is within a DataView node.
91114 //      Return a drag data object if so.
91115         getDragData: function(e) {
91116
91117 //          Use the DataView's own itemSelector (a mandatory property) to
91118 //          test if the mousedown is within one of the DataView's nodes.
91119             var sourceEl = e.getTarget(v.itemSelector, 10);
91120
91121 //          If the mousedown is within a DataView node, clone the node to produce
91122 //          a ddel element for use by the drag proxy. Also add application data
91123 //          to the returned data object.
91124             if (sourceEl) {
91125                 d = sourceEl.cloneNode(true);
91126                 d.id = Ext.id();
91127                 return {
91128                     ddel: d,
91129                     sourceEl: sourceEl,
91130                     repairXY: Ext.fly(sourceEl).getXY(),
91131                     sourceStore: v.store,
91132                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
91133                 }
91134             }
91135         },
91136
91137 //      Provide coordinates for the proxy to slide back to on failed drag.
91138 //      This is the original XY coordinates of the draggable element captured
91139 //      in the getDragData method.
91140         getRepairXY: function() {
91141             return this.dragData.repairXY;
91142         }
91143     });
91144 });</code></pre>
91145  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
91146  * cooperates with this DragZone.
91147  */
91148 Ext.define('Ext.dd.DragZone', {
91149
91150     extend: 'Ext.dd.DragSource',
91151
91152     /**
91153      * Creates new DragZone.
91154      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91155      * @param {Object} config
91156      */
91157     constructor : function(el, config){
91158         this.callParent([el, config]);
91159         if (this.containerScroll) {
91160             Ext.dd.ScrollManager.register(this.el);
91161         }
91162     },
91163
91164     /**
91165      * This property contains the data representing the dragged object. This data is set up by the implementation
91166      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
91167      * any other data according to the application's needs.
91168      * @type Object
91169      * @property dragData
91170      */
91171
91172     /**
91173      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
91174      * for auto scrolling during drag operations.
91175      */
91176
91177     /**
91178      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
91179      * for a valid target to drag based on the mouse down. Override this method
91180      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
91181      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
91182      * @param {Event} e The mouse down event
91183      * @return {Object} The dragData
91184      */
91185     getDragData : function(e){
91186         return Ext.dd.Registry.getHandleFromEvent(e);
91187     },
91188
91189     /**
91190      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
91191      * this.dragData.ddel
91192      * @param {Number} x The x position of the click on the dragged object
91193      * @param {Number} y The y position of the click on the dragged object
91194      * @return {Boolean} true to continue the drag, false to cancel
91195      */
91196     onInitDrag : function(x, y){
91197         this.proxy.update(this.dragData.ddel.cloneNode(true));
91198         this.onStartDrag(x, y);
91199         return true;
91200     },
91201
91202     /**
91203      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
91204      */
91205     afterRepair : function(){
91206         var me = this;
91207         if (Ext.enableFx) {
91208             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
91209         }
91210         me.dragging = false;
91211     },
91212
91213     /**
91214      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
91215      * the XY of this.dragData.ddel
91216      * @param {Event} e The mouse up event
91217      * @return {Number[]} The xy location (e.g. [100, 200])
91218      */
91219     getRepairXY : function(e){
91220         return Ext.Element.fly(this.dragData.ddel).getXY();
91221     },
91222
91223     destroy : function(){
91224         this.callParent();
91225         if (this.containerScroll) {
91226             Ext.dd.ScrollManager.unregister(this.el);
91227         }
91228     }
91229 });
91230
91231 /**
91232  * @class Ext.dd.ScrollManager
91233  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
91234  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
91235  * but you can also override most of the configs per scroll container by adding a
91236  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
91237  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
91238  * <pre><code>
91239 var el = Ext.get('scroll-ct');
91240 el.ddScrollConfig = {
91241     vthresh: 50,
91242     hthresh: -1,
91243     frequency: 100,
91244     increment: 200
91245 };
91246 Ext.dd.ScrollManager.register(el);
91247 </code></pre>
91248  * Note: This class is designed to be used in "Point Mode
91249  * @singleton
91250  */
91251 Ext.define('Ext.dd.ScrollManager', {
91252     singleton: true,
91253     requires: [
91254         'Ext.dd.DragDropManager'
91255     ],
91256
91257     constructor: function() {
91258         var ddm = Ext.dd.DragDropManager;
91259         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
91260         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
91261         this.doScroll = Ext.Function.bind(this.doScroll, this);
91262         this.ddmInstance = ddm;
91263         this.els = {};
91264         this.dragEl = null;
91265         this.proc = {};
91266     },
91267
91268     onStop: function(e){
91269         var sm = Ext.dd.ScrollManager;
91270         sm.dragEl = null;
91271         sm.clearProc();
91272     },
91273
91274     triggerRefresh: function() {
91275         if (this.ddmInstance.dragCurrent) {
91276             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
91277         }
91278     },
91279
91280     doScroll: function() {
91281         if (this.ddmInstance.dragCurrent) {
91282             var proc   = this.proc,
91283                 procEl = proc.el,
91284                 ddScrollConfig = proc.el.ddScrollConfig,
91285                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
91286
91287             if (!this.animate) {
91288                 if (procEl.scroll(proc.dir, inc)) {
91289                     this.triggerRefresh();
91290                 }
91291             } else {
91292                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
91293             }
91294         }
91295     },
91296
91297     clearProc: function() {
91298         var proc = this.proc;
91299         if (proc.id) {
91300             clearInterval(proc.id);
91301         }
91302         proc.id = 0;
91303         proc.el = null;
91304         proc.dir = "";
91305     },
91306
91307     startProc: function(el, dir) {
91308         this.clearProc();
91309         this.proc.el = el;
91310         this.proc.dir = dir;
91311         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
91312             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
91313                   ? el.ddScrollConfig.frequency
91314                   : this.frequency;
91315
91316         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
91317             this.proc.id = setInterval(this.doScroll, freq);
91318         }
91319     },
91320
91321     onFire: function(e, isDrop) {
91322         if (isDrop || !this.ddmInstance.dragCurrent) {
91323             return;
91324         }
91325         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
91326             this.dragEl = this.ddmInstance.dragCurrent;
91327             // refresh regions on drag start
91328             this.refreshCache();
91329         }
91330
91331         var xy = e.getXY(),
91332             pt = e.getPoint(),
91333             proc = this.proc,
91334             els = this.els;
91335
91336         for (var id in els) {
91337             var el = els[id], r = el._region;
91338             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
91339             if (r && r.contains(pt) && el.isScrollable()) {
91340                 if (r.bottom - pt.y <= c.vthresh) {
91341                     if(proc.el != el){
91342                         this.startProc(el, "down");
91343                     }
91344                     return;
91345                 }else if (r.right - pt.x <= c.hthresh) {
91346                     if (proc.el != el) {
91347                         this.startProc(el, "left");
91348                     }
91349                     return;
91350                 } else if(pt.y - r.top <= c.vthresh) {
91351                     if (proc.el != el) {
91352                         this.startProc(el, "up");
91353                     }
91354                     return;
91355                 } else if(pt.x - r.left <= c.hthresh) {
91356                     if (proc.el != el) {
91357                         this.startProc(el, "right");
91358                     }
91359                     return;
91360                 }
91361             }
91362         }
91363         this.clearProc();
91364     },
91365
91366     /**
91367      * Registers new overflow element(s) to auto scroll
91368      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91369      * The id of or the element to be scrolled or an array of either
91370      */
91371     register : function(el){
91372         if (Ext.isArray(el)) {
91373             for(var i = 0, len = el.length; i < len; i++) {
91374                     this.register(el[i]);
91375             }
91376         } else {
91377             el = Ext.get(el);
91378             this.els[el.id] = el;
91379         }
91380     },
91381
91382     /**
91383      * Unregisters overflow element(s) so they are no longer scrolled
91384      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91385      * The id of or the element to be removed or an array of either
91386      */
91387     unregister : function(el){
91388         if(Ext.isArray(el)){
91389             for (var i = 0, len = el.length; i < len; i++) {
91390                 this.unregister(el[i]);
91391             }
91392         }else{
91393             el = Ext.get(el);
91394             delete this.els[el.id];
91395         }
91396     },
91397
91398     /**
91399      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
91400      * trigger scrolling
91401      * @type Number
91402      */
91403     vthresh : 25,
91404     /**
91405      * The number of pixels from the right or left edge of a container the pointer needs to be to
91406      * trigger scrolling
91407      * @type Number
91408      */
91409     hthresh : 25,
91410
91411     /**
91412      * The number of pixels to scroll in each scroll increment
91413      * @type Number
91414      */
91415     increment : 100,
91416
91417     /**
91418      * The frequency of scrolls in milliseconds
91419      * @type Number
91420      */
91421     frequency : 500,
91422
91423     /**
91424      * True to animate the scroll
91425      * @type Boolean
91426      */
91427     animate: true,
91428
91429     /**
91430      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
91431      * @type Number
91432      */
91433     animDuration: 0.4,
91434
91435     /**
91436      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
91437      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
91438      * @type String
91439      */
91440     ddGroup: undefined,
91441
91442     /**
91443      * Manually trigger a cache refresh.
91444      */
91445     refreshCache : function(){
91446         var els = this.els,
91447             id;
91448         for (id in els) {
91449             if(typeof els[id] == 'object'){ // for people extending the object prototype
91450                 els[id]._region = els[id].getRegion();
91451             }
91452         }
91453     }
91454 });
91455
91456 /**
91457  * @class Ext.dd.DropTarget
91458  * @extends Ext.dd.DDTarget
91459  * A simple class that provides the basic implementation needed to make any element a drop target that can have
91460  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
91461  */
91462 Ext.define('Ext.dd.DropTarget', {
91463     extend: 'Ext.dd.DDTarget',
91464     requires: ['Ext.dd.ScrollManager'],
91465
91466     /**
91467      * Creates new DropTarget.
91468      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91469      * @param {Object} config
91470      */
91471     constructor : function(el, config){
91472         this.el = Ext.get(el);
91473
91474         Ext.apply(this, config);
91475
91476         if(this.containerScroll){
91477             Ext.dd.ScrollManager.register(this.el);
91478         }
91479
91480         this.callParent([this.el.dom, this.ddGroup || this.group,
91481               {isTarget: true}]);
91482     },
91483
91484     /**
91485      * @cfg {String} ddGroup
91486      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
91487      * interact with other drag drop objects in the same group.
91488      */
91489     /**
91490      * @cfg {String} [overClass=""]
91491      * The CSS class applied to the drop target element while the drag source is over it.
91492      */
91493     /**
91494      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
91495      * The CSS class returned to the drag source when drop is allowed.
91496      */
91497     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
91498     /**
91499      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
91500      * The CSS class returned to the drag source when drop is not allowed.
91501      */
91502     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
91503
91504     // private
91505     isTarget : true,
91506
91507     // private
91508     isNotifyTarget : true,
91509
91510     /**
91511      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
91512      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
91513      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
91514      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91515      * @param {Event} e The event
91516      * @param {Object} data An object containing arbitrary data supplied by the drag source
91517      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91518      * underlying {@link Ext.dd.StatusProxy} can be updated
91519      */
91520     notifyEnter : function(dd, e, data){
91521         if(this.overClass){
91522             this.el.addCls(this.overClass);
91523         }
91524         return this.dropAllowed;
91525     },
91526
91527     /**
91528      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
91529      * This method will be called on every mouse movement while the drag source is over the drop target.
91530      * This default implementation simply returns the dropAllowed config value.
91531      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91532      * @param {Event} e The event
91533      * @param {Object} data An object containing arbitrary data supplied by the drag source
91534      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91535      * underlying {@link Ext.dd.StatusProxy} can be updated
91536      */
91537     notifyOver : function(dd, e, data){
91538         return this.dropAllowed;
91539     },
91540
91541     /**
91542      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
91543      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
91544      * overClass (if any) from the drop element.
91545      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91546      * @param {Event} e The event
91547      * @param {Object} data An object containing arbitrary data supplied by the drag source
91548      */
91549     notifyOut : function(dd, e, data){
91550         if(this.overClass){
91551             this.el.removeCls(this.overClass);
91552         }
91553     },
91554
91555     /**
91556      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
91557      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
91558      * implementation that does something to process the drop event and returns true so that the drag source's
91559      * repair action does not run.
91560      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91561      * @param {Event} e The event
91562      * @param {Object} data An object containing arbitrary data supplied by the drag source
91563      * @return {Boolean} False if the drop was invalid.
91564      */
91565     notifyDrop : function(dd, e, data){
91566         return false;
91567     },
91568
91569     destroy : function(){
91570         this.callParent();
91571         if(this.containerScroll){
91572             Ext.dd.ScrollManager.unregister(this.el);
91573         }
91574     }
91575 });
91576
91577 /**
91578  * @class Ext.dd.Registry
91579  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
91580  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
91581  * @singleton
91582  */
91583 Ext.define('Ext.dd.Registry', {
91584     singleton: true,
91585     constructor: function() {
91586         this.elements = {}; 
91587         this.handles = {}; 
91588         this.autoIdSeed = 0;
91589     },
91590     
91591     getId: function(el, autogen){
91592         if(typeof el == "string"){
91593             return el;
91594         }
91595         var id = el.id;
91596         if(!id && autogen !== false){
91597             id = "extdd-" + (++this.autoIdSeed);
91598             el.id = id;
91599         }
91600         return id;
91601     },
91602     
91603     /**
91604      * Resgister a drag drop element
91605      * @param {String/HTMLElement} element The id or DOM node to register
91606      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
91607      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
91608      * knows how to interpret, plus there are some specific properties known to the Registry that should be
91609      * populated in the data object (if applicable):
91610      * <pre>
91611 Value      Description<br />
91612 ---------  ------------------------------------------<br />
91613 handles    Array of DOM nodes that trigger dragging<br />
91614            for the element being registered<br />
91615 isHandle   True if the element passed in triggers<br />
91616            dragging itself, else false
91617 </pre>
91618      */
91619     register : function(el, data){
91620         data = data || {};
91621         if (typeof el == "string") {
91622             el = document.getElementById(el);
91623         }
91624         data.ddel = el;
91625         this.elements[this.getId(el)] = data;
91626         if (data.isHandle !== false) {
91627             this.handles[data.ddel.id] = data;
91628         }
91629         if (data.handles) {
91630             var hs = data.handles;
91631             for (var i = 0, len = hs.length; i < len; i++) {
91632                 this.handles[this.getId(hs[i])] = data;
91633             }
91634         }
91635     },
91636
91637     /**
91638      * Unregister a drag drop element
91639      * @param {String/HTMLElement} element The id or DOM node to unregister
91640      */
91641     unregister : function(el){
91642         var id = this.getId(el, false);
91643         var data = this.elements[id];
91644         if(data){
91645             delete this.elements[id];
91646             if(data.handles){
91647                 var hs = data.handles;
91648                 for (var i = 0, len = hs.length; i < len; i++) {
91649                     delete this.handles[this.getId(hs[i], false)];
91650                 }
91651             }
91652         }
91653     },
91654
91655     /**
91656      * Returns the handle registered for a DOM Node by id
91657      * @param {String/HTMLElement} id The DOM node or id to look up
91658      * @return {Object} handle The custom handle data
91659      */
91660     getHandle : function(id){
91661         if(typeof id != "string"){ // must be element?
91662             id = id.id;
91663         }
91664         return this.handles[id];
91665     },
91666
91667     /**
91668      * Returns the handle that is registered for the DOM node that is the target of the event
91669      * @param {Event} e The event
91670      * @return {Object} handle The custom handle data
91671      */
91672     getHandleFromEvent : function(e){
91673         var t = e.getTarget();
91674         return t ? this.handles[t.id] : null;
91675     },
91676
91677     /**
91678      * Returns a custom data object that is registered for a DOM node by id
91679      * @param {String/HTMLElement} id The DOM node or id to look up
91680      * @return {Object} data The custom data
91681      */
91682     getTarget : function(id){
91683         if(typeof id != "string"){ // must be element?
91684             id = id.id;
91685         }
91686         return this.elements[id];
91687     },
91688
91689     /**
91690      * Returns a custom data object that is registered for the DOM node that is the target of the event
91691      * @param {Event} e The event
91692      * @return {Object} data The custom data
91693      */
91694     getTargetFromEvent : function(e){
91695         var t = e.getTarget();
91696         return t ? this.elements[t.id] || this.handles[t.id] : null;
91697     }
91698 });
91699 /**
91700  * @class Ext.dd.DropZone
91701  * @extends Ext.dd.DropTarget
91702
91703 This class provides a container DD instance that allows dropping on multiple child target nodes.
91704
91705 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
91706 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
91707 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
91708 mouse event to see if it has taken place within an element, or class of elements. This is easily done
91709 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91710 {@link Ext.DomQuery} selector.
91711
91712 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
91713 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
91714 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
91715 of these methods to provide application-specific behaviour for these events to update both
91716 application state, and UI state.
91717
91718 For example to make a GridPanel a cooperating target with the example illustrated in
91719 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
91720
91721     myGridPanel.on('render', function() {
91722         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
91723
91724             // If the mouse is over a grid row, return that node. This is
91725             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
91726             getTargetFromEvent: function(e) {
91727                 return e.getTarget(myGridPanel.getView().rowSelector);
91728             },
91729
91730             // On entry into a target node, highlight that node.
91731             onNodeEnter : function(target, dd, e, data){ 
91732                 Ext.fly(target).addCls('my-row-highlight-class');
91733             },
91734
91735             // On exit from a target node, unhighlight that node.
91736             onNodeOut : function(target, dd, e, data){ 
91737                 Ext.fly(target).removeCls('my-row-highlight-class');
91738             },
91739
91740             // While over a target node, return the default drop allowed class which
91741             // places a "tick" icon into the drag proxy.
91742             onNodeOver : function(target, dd, e, data){ 
91743                 return Ext.dd.DropZone.prototype.dropAllowed;
91744             },
91745
91746             // On node drop we can interrogate the target to find the underlying
91747             // application object that is the real target of the dragged data.
91748             // In this case, it is a Record in the GridPanel's Store.
91749             // We can use the data set up by the DragZone's getDragData method to read
91750             // any data we decided to attach in the DragZone's getDragData method.
91751             onNodeDrop : function(target, dd, e, data){
91752                 var rowIndex = myGridPanel.getView().findRowIndex(target);
91753                 var r = myGridPanel.getStore().getAt(rowIndex);
91754                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
91755                     ' on Record id ' + r.id);
91756                 return true;
91757             }
91758         });
91759     }
91760
91761 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
91762 cooperates with this DropZone.
91763
91764  * @markdown
91765  */
91766 Ext.define('Ext.dd.DropZone', {
91767     extend: 'Ext.dd.DropTarget',
91768     requires: ['Ext.dd.Registry'],
91769
91770     /**
91771      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
91772      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
91773      * provide your own custom lookup.
91774      * @param {Event} e The event
91775      * @return {Object} data The custom data
91776      */
91777     getTargetFromEvent : function(e){
91778         return Ext.dd.Registry.getTargetFromEvent(e);
91779     },
91780
91781     /**
91782      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
91783      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91784      * This method has no default implementation and should be overridden to provide
91785      * node-specific processing if necessary.
91786      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
91787      * {@link #getTargetFromEvent} for this node)
91788      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91789      * @param {Event} e The event
91790      * @param {Object} data An object containing arbitrary data supplied by the drag source
91791      */
91792     onNodeEnter : function(n, dd, e, data){
91793         
91794     },
91795
91796     /**
91797      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
91798      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91799      * The default implementation returns this.dropNotAllowed, so it should be
91800      * overridden to provide the proper feedback.
91801      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91802      * {@link #getTargetFromEvent} for this node)
91803      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91804      * @param {Event} e The event
91805      * @param {Object} data An object containing arbitrary data supplied by the drag source
91806      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91807      * underlying {@link Ext.dd.StatusProxy} can be updated
91808      */
91809     onNodeOver : function(n, dd, e, data){
91810         return this.dropAllowed;
91811     },
91812
91813     /**
91814      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
91815      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
91816      * node-specific processing if necessary.
91817      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91818      * {@link #getTargetFromEvent} for this node)
91819      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91820      * @param {Event} e The event
91821      * @param {Object} data An object containing arbitrary data supplied by the drag source
91822      */
91823     onNodeOut : function(n, dd, e, data){
91824         
91825     },
91826
91827     /**
91828      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
91829      * the drop node.  The default implementation returns false, so it should be overridden to provide the
91830      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
91831      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91832      * {@link #getTargetFromEvent} for this node)
91833      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91834      * @param {Event} e The event
91835      * @param {Object} data An object containing arbitrary data supplied by the drag source
91836      * @return {Boolean} True if the drop was valid, else false
91837      */
91838     onNodeDrop : function(n, dd, e, data){
91839         return false;
91840     },
91841
91842     /**
91843      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
91844      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
91845      * it should be overridden to provide the proper feedback if necessary.
91846      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91847      * @param {Event} e The event
91848      * @param {Object} data An object containing arbitrary data supplied by the drag source
91849      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91850      * underlying {@link Ext.dd.StatusProxy} can be updated
91851      */
91852     onContainerOver : function(dd, e, data){
91853         return this.dropNotAllowed;
91854     },
91855
91856     /**
91857      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
91858      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
91859      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
91860      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
91861      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91862      * @param {Event} e The event
91863      * @param {Object} data An object containing arbitrary data supplied by the drag source
91864      * @return {Boolean} True if the drop was valid, else false
91865      */
91866     onContainerDrop : function(dd, e, data){
91867         return false;
91868     },
91869
91870     /**
91871      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
91872      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
91873      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
91874      * you should override this method and provide a custom implementation.
91875      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91876      * @param {Event} e The event
91877      * @param {Object} data An object containing arbitrary data supplied by the drag source
91878      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91879      * underlying {@link Ext.dd.StatusProxy} can be updated
91880      */
91881     notifyEnter : function(dd, e, data){
91882         return this.dropNotAllowed;
91883     },
91884
91885     /**
91886      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
91887      * This method will be called on every mouse movement while the drag source is over the drop zone.
91888      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
91889      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
91890      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
91891      * registered node, it will call {@link #onContainerOver}.
91892      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91893      * @param {Event} e The event
91894      * @param {Object} data An object containing arbitrary data supplied by the drag source
91895      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91896      * underlying {@link Ext.dd.StatusProxy} can be updated
91897      */
91898     notifyOver : function(dd, e, data){
91899         var n = this.getTargetFromEvent(e);
91900         if(!n) { // not over valid drop target
91901             if(this.lastOverNode){
91902                 this.onNodeOut(this.lastOverNode, dd, e, data);
91903                 this.lastOverNode = null;
91904             }
91905             return this.onContainerOver(dd, e, data);
91906         }
91907         if(this.lastOverNode != n){
91908             if(this.lastOverNode){
91909                 this.onNodeOut(this.lastOverNode, dd, e, data);
91910             }
91911             this.onNodeEnter(n, dd, e, data);
91912             this.lastOverNode = n;
91913         }
91914         return this.onNodeOver(n, dd, e, data);
91915     },
91916
91917     /**
91918      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
91919      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
91920      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
91921      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91922      * @param {Event} e The event
91923      * @param {Object} data An object containing arbitrary data supplied by the drag zone
91924      */
91925     notifyOut : function(dd, e, data){
91926         if(this.lastOverNode){
91927             this.onNodeOut(this.lastOverNode, dd, e, data);
91928             this.lastOverNode = null;
91929         }
91930     },
91931
91932     /**
91933      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
91934      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
91935      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
91936      * otherwise it will call {@link #onContainerDrop}.
91937      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91938      * @param {Event} e The event
91939      * @param {Object} data An object containing arbitrary data supplied by the drag source
91940      * @return {Boolean} False if the drop was invalid.
91941      */
91942     notifyDrop : function(dd, e, data){
91943         if(this.lastOverNode){
91944             this.onNodeOut(this.lastOverNode, dd, e, data);
91945             this.lastOverNode = null;
91946         }
91947         var n = this.getTargetFromEvent(e);
91948         return n ?
91949             this.onNodeDrop(n, dd, e, data) :
91950             this.onContainerDrop(dd, e, data);
91951     },
91952
91953     // private
91954     triggerCacheRefresh : function() {
91955         Ext.dd.DDM.refreshCache(this.groups);
91956     }
91957 });
91958 /**
91959  * @class Ext.flash.Component
91960  * @extends Ext.Component
91961  *
91962  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
91963  * in layout like any other Component.
91964  *
91965  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
91966  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
91967  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
91968  * and then simply import it into the head of your HTML document:
91969  *
91970  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
91971  *
91972  * ## Configuration
91973  *
91974  * This component allows several options for configuring how the target Flash movie is embedded. The most
91975  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
91976  * configurations include:
91977  *
91978  * - {@link #backgroundColor}
91979  * - {@link #wmode}
91980  * - {@link #flashVars}
91981  * - {@link #flashParams}
91982  * - {@link #flashAttributes}
91983  *
91984  * ## Example usage:
91985  *
91986  *     var win = Ext.widget('window', {
91987  *         title: "It's a tiger!",
91988  *         layout: 'fit',
91989  *         width: 300,
91990  *         height: 300,
91991  *         x: 20,
91992  *         y: 20,
91993  *         resizable: true,
91994  *         items: {
91995  *             xtype: 'flash',
91996  *             url: 'tiger.swf'
91997  *         }
91998  *     });
91999  *     win.show();
92000  *
92001  * ## Express Install
92002  *
92003  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
92004  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
92005  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
92006  *
92007  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
92008  *
92009  * @docauthor Jason Johnston <jason@sencha.com>
92010  */
92011 Ext.define('Ext.flash.Component', {
92012     extend: 'Ext.Component',
92013     alternateClassName: 'Ext.FlashComponent',
92014     alias: 'widget.flash',
92015
92016     /**
92017      * @cfg {String} flashVersion
92018      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
92019      */
92020     flashVersion : '9.0.115',
92021
92022     /**
92023      * @cfg {String} backgroundColor
92024      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
92025      */
92026     backgroundColor: '#ffffff',
92027
92028     /**
92029      * @cfg {String} wmode
92030      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
92031      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
92032      * movie transparent.
92033      */
92034     wmode: 'opaque',
92035
92036     /**
92037      * @cfg {Object} flashVars
92038      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
92039      */
92040
92041     /**
92042      * @cfg {Object} flashParams
92043      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
92044      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
92045      */
92046
92047     /**
92048      * @cfg {Object} flashAttributes
92049      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
92050      */
92051
92052     /**
92053      * @cfg {String} url
92054      * The URL of the SWF file to include. Required.
92055      */
92056
92057     /**
92058      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
92059      * so that the movie matches the width of the component.
92060      */
92061     swfWidth: '100%',
92062
92063     /**
92064      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
92065      * so that the movie matches the height of the component.
92066      */
92067     swfHeight: '100%',
92068
92069     /**
92070      * @cfg {Boolean} expressInstall
92071      * True to prompt the user to install flash if not installed. Note that this uses
92072      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
92073      */
92074     expressInstall: false,
92075
92076     /**
92077      * @property swf
92078      * @type {Ext.Element}
92079      * A reference to the object or embed element into which the SWF file is loaded. Only
92080      * populated after the component is rendered and the SWF has been successfully embedded.
92081      */
92082
92083     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
92084     renderTpl: ['<div id="{swfId}"></div>'],
92085
92086     initComponent: function() {
92087
92088         this.callParent();
92089         this.addEvents(
92090             /**
92091              * @event success
92092              * Fired when the Flash movie has been successfully embedded
92093              * @param {Ext.flash.Component} this
92094              */
92095             'success',
92096
92097             /**
92098              * @event failure
92099              * Fired when the Flash movie embedding fails
92100              * @param {Ext.flash.Component} this
92101              */
92102             'failure'
92103         );
92104     },
92105
92106     onRender: function() {
92107         var me = this,
92108             params, vars, undef,
92109             swfId = me.getSwfId();
92110
92111         me.renderData.swfId = swfId;
92112
92113         me.callParent(arguments);
92114
92115         params = Ext.apply({
92116             allowScriptAccess: 'always',
92117             bgcolor: me.backgroundColor,
92118             wmode: me.wmode
92119         }, me.flashParams);
92120
92121         vars = Ext.apply({
92122             allowedDomain: document.location.hostname
92123         }, me.flashVars);
92124
92125         new swfobject.embedSWF(
92126             me.url,
92127             swfId,
92128             me.swfWidth,
92129             me.swfHeight,
92130             me.flashVersion,
92131             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
92132             vars,
92133             params,
92134             me.flashAttributes,
92135             Ext.bind(me.swfCallback, me)
92136         );
92137     },
92138
92139     /**
92140      * @private
92141      * The callback method for handling an embedding success or failure by SWFObject
92142      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
92143      */
92144     swfCallback: function(e) {
92145         var me = this;
92146         if (e.success) {
92147             me.swf = Ext.get(e.ref);
92148             me.onSuccess();
92149             me.fireEvent('success', me);
92150         } else {
92151             me.onFailure();
92152             me.fireEvent('failure', me);
92153         }
92154     },
92155
92156     /**
92157      * Retrieve the id of the SWF object/embed element
92158      */
92159     getSwfId: function() {
92160         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
92161     },
92162
92163     onSuccess: function() {
92164         // swfobject forces visiblity:visible on the swf element, which prevents it 
92165         // from getting hidden when an ancestor is given visibility:hidden.
92166         this.swf.setStyle('visibility', 'inherit');
92167     },
92168
92169     onFailure: Ext.emptyFn,
92170
92171     beforeDestroy: function() {
92172         var me = this,
92173             swf = me.swf;
92174         if (swf) {
92175             swfobject.removeSWF(me.getSwfId());
92176             Ext.destroy(swf);
92177             delete me.swf;
92178         }
92179         me.callParent();
92180     },
92181
92182     statics: {
92183         /**
92184          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
92185          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
92186          * @static
92187          * @type String
92188          */
92189         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
92190     }
92191 });
92192
92193 /**
92194  * @class Ext.form.action.Action
92195  * @extends Ext.Base
92196  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
92197  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92198  * the Form needs to perform an action such as submit or load. The Configuration options
92199  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
92200  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
92201  * <p>The instance of Action which performed the action is passed to the success
92202  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
92203  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
92204  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
92205  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
92206  */
92207 Ext.define('Ext.form.action.Action', {
92208     alternateClassName: 'Ext.form.Action',
92209
92210     /**
92211      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
92212      * is invoking this Action. Required.
92213      */
92214
92215     /**
92216      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
92217      * configured on the {@link #form}.
92218      */
92219
92220     /**
92221      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
92222      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
92223      * before the {@link #success} callback is called and before the Form's
92224      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
92225      */
92226
92227     /**
92228      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
92229      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
92230      */
92231
92232     /**
92233      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
92234      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
92235      * input fields.</p>
92236      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92237      */
92238
92239     /**
92240      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
92241      * {@link Ext.data.proxy.Ajax#headers}.</p>
92242      */
92243
92244     /**
92245      * @cfg {Number} timeout The number of seconds to wait for a server response before
92246      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
92247      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
92248      * {@link #form}.
92249      */
92250
92251     /**
92252      * @cfg {Function} success The function to call when a valid success return packet is received.
92253      * The function is passed the following parameters:<ul class="mdetail-params">
92254      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92255      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
92256      * property of this object may be examined to perform custom postprocessing.</div></li>
92257      * </ul>
92258      */
92259
92260     /**
92261      * @cfg {Function} failure The function to call when a failure packet was received, or when an
92262      * error ocurred in the Ajax communication.
92263      * The function is passed the following parameters:<ul class="mdetail-params">
92264      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92265      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
92266      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
92267      * property of this object may be examined to perform custom postprocessing.</div></li>
92268      * </ul>
92269      */
92270
92271     /**
92272      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
92273      * callback functions (the <tt>this</tt> reference for the callback functions).
92274      */
92275
92276     /**
92277      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
92278      * during the time the action is being processed.
92279      */
92280
92281     /**
92282      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
92283      * during the time the action is being processed.
92284      */
92285
92286     /**
92287      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
92288      * when it is submitted. Defaults to <tt>true</tt>.
92289      */
92290     submitEmptyText : true,
92291     /**
92292      * @property type
92293      * The type of action this Action instance performs.
92294      * Currently only "submit" and "load" are supported.
92295      * @type {String}
92296      */
92297
92298     /**
92299      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
92300      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
92301      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
92302      * <pre><code>
92303 var fp = new Ext.form.Panel({
92304 ...
92305 buttons: [{
92306     text: 'Save',
92307     formBind: true,
92308     handler: function(){
92309         if(fp.getForm().isValid()){
92310             fp.getForm().submit({
92311                 url: 'form-submit.php',
92312                 waitMsg: 'Submitting your data...',
92313                 success: function(form, action){
92314                     // server responded with success = true
92315                     var result = action.{@link #result};
92316                 },
92317                 failure: function(form, action){
92318                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
92319                         Ext.Msg.alert('Error',
92320                             'Status:'+action.{@link #response}.status+': '+
92321                             action.{@link #response}.statusText);
92322                     }
92323                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
92324                         // server responded with success = false
92325                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
92326                     }
92327                 }
92328             });
92329         }
92330     }
92331 },{
92332     text: 'Reset',
92333     handler: function(){
92334         fp.getForm().reset();
92335     }
92336 }]
92337      * </code></pre>
92338      * @property failureType
92339      * @type {String}
92340      */
92341
92342     /**
92343      * The raw XMLHttpRequest object used to perform the action.
92344      * @property response
92345      * @type {Object}
92346      */
92347
92348     /**
92349      * The decoded response object containing a boolean <tt>success</tt> property and
92350      * other, action-specific properties.
92351      * @property result
92352      * @type {Object}
92353      */
92354
92355     /**
92356      * Creates new Action.
92357      * @param {Object} config (optional) Config object.
92358      */
92359     constructor: function(config) {
92360         if (config) {
92361             Ext.apply(this, config);
92362         }
92363
92364         // Normalize the params option to an Object
92365         var params = config.params;
92366         if (Ext.isString(params)) {
92367             this.params = Ext.Object.fromQueryString(params);
92368         }
92369     },
92370
92371     /**
92372      * Invokes this action using the current configuration.
92373      */
92374     run: Ext.emptyFn,
92375
92376     /**
92377      * @private
92378      * @method onSuccess
92379      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
92380      * @param {Object} response
92381      */
92382
92383     /**
92384      * @private
92385      * @method handleResponse
92386      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
92387      * @param {Object} response
92388      */
92389
92390     /**
92391      * @private
92392      * Handles a failure response.
92393      * @param {Object} response
92394      */
92395     onFailure : function(response){
92396         this.response = response;
92397         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
92398         this.form.afterAction(this, false);
92399     },
92400
92401     /**
92402      * @private
92403      * Validates that a response contains either responseText or responseXML and invokes
92404      * {@link #handleResponse} to build the result object.
92405      * @param {Object} response The raw response object.
92406      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
92407      *                         the response had empty responseText and responseXML.
92408      */
92409     processResponse : function(response){
92410         this.response = response;
92411         if (!response.responseText && !response.responseXML) {
92412             return true;
92413         }
92414         return (this.result = this.handleResponse(response));
92415     },
92416
92417     /**
92418      * @private
92419      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
92420      * @return {String} The URL.
92421      */
92422     getUrl: function() {
92423         return this.url || this.form.url;
92424     },
92425
92426     /**
92427      * @private
92428      * Determine the HTTP method to be used for the request.
92429      * @return {String} The HTTP method
92430      */
92431     getMethod: function() {
92432         return (this.method || this.form.method || 'POST').toUpperCase();
92433     },
92434
92435     /**
92436      * @private
92437      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
92438      * Items in params override items of the same name in baseParams.
92439      * @return {Object} the full set of parameters
92440      */
92441     getParams: function() {
92442         return Ext.apply({}, this.params, this.form.baseParams);
92443     },
92444
92445     /**
92446      * @private
92447      * Creates a callback object.
92448      */
92449     createCallback: function() {
92450         var me = this,
92451             undef,
92452             form = me.form;
92453         return {
92454             success: me.onSuccess,
92455             failure: me.onFailure,
92456             scope: me,
92457             timeout: (this.timeout * 1000) || (form.timeout * 1000),
92458             upload: form.fileUpload ? me.onSuccess : undef
92459         };
92460     },
92461
92462     statics: {
92463         /**
92464          * @property CLIENT_INVALID
92465          * Failure type returned when client side validation of the Form fails
92466          * thus aborting a submit action. Client side validation is performed unless
92467          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
92468          * @type {String}
92469          * @static
92470          */
92471         CLIENT_INVALID: 'client',
92472
92473         /**
92474          * @property SERVER_INVALID
92475          * <p>Failure type returned when server side processing fails and the {@link #result}'s
92476          * <tt>success</tt> property is set to <tt>false</tt>.</p>
92477          * <p>In the case of a form submission, field-specific error messages may be returned in the
92478          * {@link #result}'s <tt>errors</tt> property.</p>
92479          * @type {String}
92480          * @static
92481          */
92482         SERVER_INVALID: 'server',
92483
92484         /**
92485          * @property CONNECT_FAILURE
92486          * Failure type returned when a communication error happens when attempting
92487          * to send a request to the remote server. The {@link #response} may be examined to
92488          * provide further information.
92489          * @type {String}
92490          * @static
92491          */
92492         CONNECT_FAILURE: 'connect',
92493
92494         /**
92495          * @property LOAD_FAILURE
92496          * Failure type returned when the response's <tt>success</tt>
92497          * property is set to <tt>false</tt>, or no field values are returned in the response's
92498          * <tt>data</tt> property.
92499          * @type {String}
92500          * @static
92501          */
92502         LOAD_FAILURE: 'load'
92503
92504
92505     }
92506 });
92507
92508 /**
92509  * @class Ext.form.action.Submit
92510  * @extends Ext.form.action.Action
92511  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
92512  * and processes the returned response.</p>
92513  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92514  * {@link Ext.form.Basic#submit submit}ting.</p>
92515  * <p><u><b>Response Packet Criteria</b></u></p>
92516  * <p>A response packet may contain:
92517  * <div class="mdetail-params"><ul>
92518  * <li><b><code>success</code></b> property : Boolean
92519  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
92520  * <li><b><code>errors</code></b> property : Object
92521  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
92522  * which is optional, contains error messages for invalid fields.</div></li>
92523  * </ul></div>
92524  * <p><u><b>JSON Packets</b></u></p>
92525  * <p>By default, response packets are assumed to be JSON, so a typical response
92526  * packet may look like this:</p><pre><code>
92527 {
92528     success: false,
92529     errors: {
92530         clientCode: "Client not found",
92531         portOfLoading: "This field must not be null"
92532     }
92533 }</code></pre>
92534  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
92535  * or event handler methods. The object decoded from this JSON is available in the
92536  * {@link Ext.form.action.Action#result result} property.</p>
92537  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
92538     errorReader: new Ext.data.reader.Xml({
92539             record : 'field',
92540             success: '@success'
92541         }, [
92542             'id', 'msg'
92543         ]
92544     )
92545 </code></pre>
92546  * <p>then the results may be sent back in XML format:</p><pre><code>
92547 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
92548 &lt;message success="false"&gt;
92549 &lt;errors&gt;
92550     &lt;field&gt;
92551         &lt;id&gt;clientCode&lt;/id&gt;
92552         &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;
92553     &lt;/field&gt;
92554     &lt;field&gt;
92555         &lt;id&gt;portOfLoading&lt;/id&gt;
92556         &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;
92557     &lt;/field&gt;
92558 &lt;/errors&gt;
92559 &lt;/message&gt;
92560 </code></pre>
92561  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
92562  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
92563  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
92564  */
92565 Ext.define('Ext.form.action.Submit', {
92566     extend:'Ext.form.action.Action',
92567     alternateClassName: 'Ext.form.Action.Submit',
92568     alias: 'formaction.submit',
92569
92570     type: 'submit',
92571
92572     /**
92573      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
92574      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
92575      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
92576      */
92577
92578     // inherit docs
92579     run : function(){
92580         var form = this.form;
92581         if (this.clientValidation === false || form.isValid()) {
92582             this.doSubmit();
92583         } else {
92584             // client validation failed
92585             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
92586             form.afterAction(this, false);
92587         }
92588     },
92589
92590     /**
92591      * @private
92592      * Perform the submit of the form data.
92593      */
92594     doSubmit: function() {
92595         var formEl,
92596             ajaxOptions = Ext.apply(this.createCallback(), {
92597                 url: this.getUrl(),
92598                 method: this.getMethod(),
92599                 headers: this.headers
92600             });
92601
92602         // For uploads we need to create an actual form that contains the file upload fields,
92603         // and pass that to the ajax call so it can do its iframe-based submit method.
92604         if (this.form.hasUpload()) {
92605             formEl = ajaxOptions.form = this.buildForm();
92606             ajaxOptions.isUpload = true;
92607         } else {
92608             ajaxOptions.params = this.getParams();
92609         }
92610
92611         Ext.Ajax.request(ajaxOptions);
92612
92613         if (formEl) {
92614             Ext.removeNode(formEl);
92615         }
92616     },
92617
92618     /**
92619      * @private
92620      * Build the full set of parameters from the field values plus any additional configured params.
92621      */
92622     getParams: function() {
92623         var nope = false,
92624             configParams = this.callParent(),
92625             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
92626         return Ext.apply({}, fieldParams, configParams);
92627     },
92628
92629     /**
92630      * @private
92631      * Build a form element containing fields corresponding to all the parameters to be
92632      * submitted (everything returned by {@link #getParams}.
92633      * NOTE: the form element is automatically added to the DOM, so any code that uses
92634      * it must remove it from the DOM after finishing with it.
92635      * @return HTMLFormElement
92636      */
92637     buildForm: function() {
92638         var fieldsSpec = [],
92639             formSpec,
92640             formEl,
92641             basicForm = this.form,
92642             params = this.getParams(),
92643             uploadFields = [];
92644
92645         basicForm.getFields().each(function(field) {
92646             if (field.isFileUpload()) {
92647                 uploadFields.push(field);
92648             }
92649         });
92650
92651         function addField(name, val) {
92652             fieldsSpec.push({
92653                 tag: 'input',
92654                 type: 'hidden',
92655                 name: name,
92656                 value: Ext.String.htmlEncode(val)
92657             });
92658         }
92659
92660         // Add the form field values
92661         Ext.iterate(params, function(key, val) {
92662             if (Ext.isArray(val)) {
92663                 Ext.each(val, function(v) {
92664                     addField(key, v);
92665                 });
92666             } else {
92667                 addField(key, val);
92668             }
92669         });
92670
92671         formSpec = {
92672             tag: 'form',
92673             action: this.getUrl(),
92674             method: this.getMethod(),
92675             target: this.target || '_self',
92676             style: 'display:none',
92677             cn: fieldsSpec
92678         };
92679
92680         // Set the proper encoding for file uploads
92681         if (uploadFields.length) {
92682             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
92683         }
92684
92685         // Create the form
92686         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
92687
92688         // Special handling for file upload fields: since browser security measures prevent setting
92689         // their values programatically, and prevent carrying their selected values over when cloning,
92690         // we have to move the actual field instances out of their components and into the form.
92691         Ext.Array.each(uploadFields, function(field) {
92692             if (field.rendered) { // can only have a selected file value after being rendered
92693                 formEl.appendChild(field.extractFileInput());
92694             }
92695         });
92696
92697         return formEl;
92698     },
92699
92700
92701
92702     /**
92703      * @private
92704      */
92705     onSuccess: function(response) {
92706         var form = this.form,
92707             success = true,
92708             result = this.processResponse(response);
92709         if (result !== true && !result.success) {
92710             if (result.errors) {
92711                 form.markInvalid(result.errors);
92712             }
92713             this.failureType = Ext.form.action.Action.SERVER_INVALID;
92714             success = false;
92715         }
92716         form.afterAction(this, success);
92717     },
92718
92719     /**
92720      * @private
92721      */
92722     handleResponse: function(response) {
92723         var form = this.form,
92724             errorReader = form.errorReader,
92725             rs, errors, i, len, records;
92726         if (errorReader) {
92727             rs = errorReader.read(response);
92728             records = rs.records;
92729             errors = [];
92730             if (records) {
92731                 for(i = 0, len = records.length; i < len; i++) {
92732                     errors[i] = records[i].data;
92733                 }
92734             }
92735             if (errors.length < 1) {
92736                 errors = null;
92737             }
92738             return {
92739                 success : rs.success,
92740                 errors : errors
92741             };
92742         }
92743         return Ext.decode(response.responseText);
92744     }
92745 });
92746
92747 /**
92748  * @class Ext.util.ComponentDragger
92749  * @extends Ext.dd.DragTracker
92750  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
92751  * <p>This is configured with a Component to be made draggable, and a config object for the
92752  * {@link Ext.dd.DragTracker} class.</p>
92753  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
92754  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
92755  */
92756 Ext.define('Ext.util.ComponentDragger', {
92757
92758     /**
92759      * @cfg {Boolean} constrain
92760      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
92761      */
92762
92763     /**
92764      * @cfg {String/Ext.Element} delegate
92765      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
92766      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
92767      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
92768      */
92769
92770     /**
92771      * @cfg {Boolean} constrainDelegate
92772      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
92773      */
92774
92775     extend: 'Ext.dd.DragTracker',
92776
92777     autoStart: 500,
92778
92779     /**
92780      * Creates new ComponentDragger.
92781      * @param {Object} comp The Component to provide dragging for.
92782      * @param {Object} config (optional) Config object
92783      */
92784     constructor: function(comp, config) {
92785         this.comp = comp;
92786         this.initialConstrainTo = config.constrainTo;
92787         this.callParent([ config ]);
92788     },
92789
92790     onStart: function(e) {
92791         var me = this,
92792             comp = me.comp;
92793
92794         // Cache the start [X, Y] array
92795         this.startPosition = comp.getPosition();
92796
92797         // If client Component has a ghost method to show a lightweight version of itself
92798         // then use that as a drag proxy unless configured to liveDrag.
92799         if (comp.ghost && !comp.liveDrag) {
92800              me.proxy = comp.ghost();
92801              me.dragTarget = me.proxy.header.el;
92802         }
92803
92804         // Set the constrainTo Region before we start dragging.
92805         if (me.constrain || me.constrainDelegate) {
92806             me.constrainTo = me.calculateConstrainRegion();
92807         }
92808     },
92809
92810     calculateConstrainRegion: function() {
92811         var me = this,
92812             comp = me.comp,
92813             c = me.initialConstrainTo,
92814             delegateRegion,
92815             elRegion,
92816             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
92817
92818         // The configured constrainTo might be a Region or an element
92819         if (!(c instanceof Ext.util.Region)) {
92820             c =  Ext.fly(c).getViewRegion();
92821         }
92822
92823         // Reduce the constrain region to allow for shadow
92824         if (shadowSize) {
92825             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
92826         }
92827
92828         // If they only want to constrain the *delegate* to within the constrain region,
92829         // adjust the region to be larger based on the insets of the delegate from the outer
92830         // edges of the Component.
92831         if (!me.constrainDelegate) {
92832             delegateRegion = Ext.fly(me.dragTarget).getRegion();
92833             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
92834
92835             c.adjust(
92836                 delegateRegion.top - elRegion.top,
92837                 delegateRegion.right - elRegion.right,
92838                 delegateRegion.bottom - elRegion.bottom,
92839                 delegateRegion.left - elRegion.left
92840             );
92841         }
92842         return c;
92843     },
92844
92845     // Move either the ghost Component or the target Component to its new position on drag
92846     onDrag: function(e) {
92847         var me = this,
92848             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
92849             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
92850
92851         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
92852     },
92853
92854     onEnd: function(e) {
92855         if (this.proxy && !this.comp.liveDrag) {
92856             this.comp.unghost();
92857         }
92858     }
92859 });
92860 /**
92861  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
92862  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
92863  * to let them be managed by the Field layout.
92864  *
92865  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
92866  * is more likely you will want to use one of the component classes that import this mixin, such as
92867  * Ext.form.field.Base or Ext.form.FieldContainer.
92868  *
92869  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
92870  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
92871  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
92872  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
92873  * two.)
92874  *
92875  * Component classes which use this mixin should use the Field layout
92876  * or a derivation thereof to properly size and position the label and message according to the component config.
92877  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
92878  * set up correctly.
92879  *
92880  * @docauthor Jason Johnston <jason@sencha.com>
92881  */
92882 Ext.define("Ext.form.Labelable", {
92883     requires: ['Ext.XTemplate'],
92884
92885     /**
92886      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
92887      * The rendering template for the field decorations. Component classes using this mixin should include
92888      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
92889      * {@link #getSubTplMarkup} method to generate the field body content.
92890      */
92891     labelableRenderTpl: [
92892         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
92893             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
92894                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
92895                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
92896             '</label>',
92897         '</tpl>',
92898         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
92899         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
92900         '<div class="{clearCls}" role="presentation"><!-- --></div>',
92901         {
92902             compiled: true,
92903             disableFormats: true
92904         }
92905     ],
92906
92907     /**
92908      * @cfg {Ext.XTemplate} activeErrorsTpl
92909      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
92910      * into a single HTML string. By default this renders each message as an item in an unordered list.
92911      */
92912     activeErrorsTpl: [
92913         '<tpl if="errors && errors.length">',
92914             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
92915         '</tpl>'
92916     ],
92917
92918     /**
92919      * @property isFieldLabelable
92920      * @type Boolean
92921      * Flag denoting that this object is labelable as a field. Always true.
92922      */
92923     isFieldLabelable: true,
92924
92925     /**
92926      * @cfg {String} [formItemCls='x-form-item']
92927      * A CSS class to be applied to the outermost element to denote that it is participating in the form
92928      * field layout.
92929      */
92930     formItemCls: Ext.baseCSSPrefix + 'form-item',
92931
92932     /**
92933      * @cfg {String} [labelCls='x-form-item-label']
92934      * The CSS class to be applied to the label element.
92935      * This (single) CSS class is used to formulate the renderSelector and drives the field
92936      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
92937      * additional classes, use {@link #labelClsExtra}.
92938      */
92939     labelCls: Ext.baseCSSPrefix + 'form-item-label',
92940
92941     /**
92942      * @cfg {String} labelClsExtra
92943      * An optional string of one or more additional CSS classes to add to the label element.
92944      * Defaults to empty.
92945      */
92946
92947     /**
92948      * @cfg {String} [errorMsgCls='x-form-error-msg']
92949      * The CSS class to be applied to the error message element.
92950      */
92951     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
92952
92953     /**
92954      * @cfg {String} [baseBodyCls='x-form-item-body']
92955      * The CSS class to be applied to the body content element.
92956      */
92957     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
92958
92959     /**
92960      * @cfg {String} fieldBodyCls
92961      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
92962      */
92963     fieldBodyCls: '',
92964
92965     /**
92966      * @cfg {String} [clearCls='x-clear']
92967      * The CSS class to be applied to the special clearing div rendered directly after the field
92968      * contents wrapper to provide field clearing.
92969      */
92970     clearCls: Ext.baseCSSPrefix + 'clear',
92971
92972     /**
92973      * @cfg {String} [invalidCls='x-form-invalid']
92974      * The CSS class to use when marking the component invalid.
92975      */
92976     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
92977
92978     /**
92979      * @cfg {String} fieldLabel
92980      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
92981      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
92982      * configs.
92983      */
92984     fieldLabel: undefined,
92985
92986     /**
92987      * @cfg {String} labelAlign
92988      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
92989      * <ul>
92990      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
92991      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
92992      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
92993      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
92994      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
92995      * </ul>
92996      */
92997     labelAlign : 'left',
92998
92999     /**
93000      * @cfg {Number} labelWidth
93001      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
93002      * to "left" or "right".
93003      */
93004     labelWidth: 100,
93005
93006     /**
93007      * @cfg {Number} labelPad
93008      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
93009      */
93010     labelPad : 5,
93011
93012     /**
93013      * @cfg {String} labelSeparator
93014      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
93015      */
93016     labelSeparator : ':',
93017
93018     /**
93019      * @cfg {String} labelStyle
93020      * A CSS style specification string to apply directly to this field's label.
93021      */
93022
93023     /**
93024      * @cfg {Boolean} hideLabel
93025      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
93026      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
93027      */
93028     hideLabel: false,
93029
93030     /**
93031      * @cfg {Boolean} hideEmptyLabel
93032      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
93033      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
93034      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
93035      * to line up with other labeled fields in the same form.</p>
93036      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
93037      * the {@link #hideLabel} config to <tt>true</tt>.</p>
93038      */
93039     hideEmptyLabel: true,
93040
93041     /**
93042      * @cfg {Boolean} preventMark
93043      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
93044      */
93045     preventMark: false,
93046
93047     /**
93048      * @cfg {Boolean} autoFitErrors
93049      * Whether to adjust the component's body area to make room for 'side' or 'under'
93050      * {@link #msgTarget error messages}.
93051      */
93052     autoFitErrors: true,
93053
93054     /**
93055      * @cfg {String} msgTarget <p>The location where the error message text should display.
93056      * Must be one of the following values:</p>
93057      * <div class="mdetail-params"><ul>
93058      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
93059      * <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>
93060      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
93061      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
93062      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
93063      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
93064      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
93065      * </ul></div>
93066      */
93067     msgTarget: 'qtip',
93068
93069     /**
93070      * @cfg {String} activeError
93071      * If specified, then the component will be displayed with this value as its active error when
93072      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
93073      * change it after component creation.
93074      */
93075
93076
93077     /**
93078      * Performs initialization of this mixin. Component classes using this mixin should call this method
93079      * during their own initialization.
93080      */
93081     initLabelable: function() {
93082         this.addCls(this.formItemCls);
93083
93084         this.addEvents(
93085             /**
93086              * @event errorchange
93087              * Fires when the active error message is changed via {@link #setActiveError}.
93088              * @param {Ext.form.Labelable} this
93089              * @param {String} error The active error message
93090              */
93091             'errorchange'
93092         );
93093     },
93094
93095     /**
93096      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
93097      * overridden to provide
93098      * @return {String} The configured field label, or empty string if not defined
93099      */
93100     getFieldLabel: function() {
93101         return this.fieldLabel || '';
93102     },
93103
93104     /**
93105      * @protected
93106      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
93107      * @return {Object} The template arguments
93108      */
93109     getLabelableRenderData: function() {
93110         var me = this,
93111             labelAlign = me.labelAlign,
93112             labelCls = me.labelCls,
93113             labelClsExtra = me.labelClsExtra,
93114             labelPad = me.labelPad,
93115             labelStyle;
93116
93117         // Calculate label styles up front rather than in the Field layout for speed; this
93118         // is safe because label alignment/width/pad are not expected to change.
93119         if (labelAlign === 'top') {
93120             labelStyle = 'margin-bottom:' + labelPad + 'px;';
93121         } else {
93122             labelStyle = 'margin-right:' + labelPad + 'px;';
93123             // Add the width for border-box browsers; will be set by the Field layout for content-box
93124             if (Ext.isBorderBox) {
93125                 labelStyle += 'width:' + me.labelWidth + 'px;';
93126             }
93127         }
93128
93129         return Ext.copyTo(
93130             {
93131                 inputId: me.getInputId(),
93132                 fieldLabel: me.getFieldLabel(),
93133                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
93134                 labelStyle: labelStyle + (me.labelStyle || ''),
93135                 subTplMarkup: me.getSubTplMarkup()
93136             },
93137             me,
93138             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
93139             true
93140         );
93141     },
93142
93143     onLabelableRender: function () {
93144         this.addChildEls(
93145             /**
93146              * @property labelEl
93147              * @type Ext.Element
93148              * The label Element for this component. Only available after the component has been rendered.
93149              */
93150             'labelEl',
93151
93152             /**
93153              * @property bodyEl
93154              * @type Ext.Element
93155              * The div Element wrapping the component's contents. Only available after the component has been rendered.
93156              */
93157             'bodyEl',
93158
93159             /**
93160              * @property errorEl
93161              * @type Ext.Element
93162              * The div Element that will contain the component's error message(s). Note that depending on the
93163              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
93164              * presentation, but will always be present in the DOM for use by assistive technologies.
93165              */
93166             'errorEl'
93167         );
93168     },
93169
93170     /**
93171      * @protected
93172      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
93173      * be implemented by classes including this mixin as needed.
93174      * @return {String} The markup to be inserted
93175      */
93176     getSubTplMarkup: function() {
93177         return '';
93178     },
93179
93180     /**
93181      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
93182      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
93183      * @return {String} The input id
93184      */
93185     getInputId: function() {
93186         return '';
93187     },
93188
93189     /**
93190      * Gets the active error message for this component, if any. This does not trigger
93191      * validation on its own, it merely returns any message that the component may already hold.
93192      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
93193      */
93194     getActiveError : function() {
93195         return this.activeError || '';
93196     },
93197
93198     /**
93199      * Tells whether the field currently has an active error message. This does not trigger
93200      * validation on its own, it merely looks for any message that the component may already hold.
93201      * @return {Boolean}
93202      */
93203     hasActiveError: function() {
93204         return !!this.getActiveError();
93205     },
93206
93207     /**
93208      * Sets the active error message to the given string. This replaces the entire error message
93209      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
93210      * messages and formats them according to the {@link #activeErrorsTpl}.
93211      *
93212      * Note that this only updates the error message element's text and attributes, you'll have
93213      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93214      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93215      *
93216      * @param {String} msg The error message
93217      */
93218     setActiveError: function(msg) {
93219         this.activeError = msg;
93220         this.activeErrors = [msg];
93221         this.renderActiveError();
93222     },
93223
93224     /**
93225      * Gets an Array of any active error messages currently applied to the field. This does not trigger
93226      * validation on its own, it merely returns any messages that the component may already hold.
93227      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
93228      */
93229     getActiveErrors: function() {
93230         return this.activeErrors || [];
93231     },
93232
93233     /**
93234      * Set the active error message to an Array of error messages. The messages are formatted into
93235      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
93236      * which allows setting the entire error contents with a single string.
93237      *
93238      * Note that this only updates the error message element's text and attributes, you'll have
93239      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93240      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93241      *
93242      * @param {String[]} errors The error messages
93243      */
93244     setActiveErrors: function(errors) {
93245         this.activeErrors = errors;
93246         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
93247         this.renderActiveError();
93248     },
93249
93250     /**
93251      * Clears the active error message(s).
93252      *
93253      * Note that this only clears the error message element's text and attributes, you'll have
93254      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93255      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
93256      */
93257     unsetActiveError: function() {
93258         delete this.activeError;
93259         delete this.activeErrors;
93260         this.renderActiveError();
93261     },
93262
93263     /**
93264      * @private
93265      * Updates the rendered DOM to match the current activeError. This only updates the content and
93266      * attributes, you'll have to call doComponentLayout to actually update the display.
93267      */
93268     renderActiveError: function() {
93269         var me = this,
93270             activeError = me.getActiveError(),
93271             hasError = !!activeError;
93272
93273         if (activeError !== me.lastActiveError) {
93274             me.fireEvent('errorchange', me, activeError);
93275             me.lastActiveError = activeError;
93276         }
93277
93278         if (me.rendered && !me.isDestroyed && !me.preventMark) {
93279             // Add/remove invalid class
93280             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
93281
93282             // Update the aria-invalid attribute
93283             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
93284
93285             // Update the errorEl with the error message text
93286             me.errorEl.dom.innerHTML = activeError;
93287         }
93288     },
93289
93290     /**
93291      * Applies a set of default configuration values to this Labelable instance. For each of the
93292      * properties in the given object, check if this component hasOwnProperty that config; if not
93293      * then it's inheriting a default value from its prototype and we should apply the default value.
93294      * @param {Object} defaults The defaults to apply to the object.
93295      */
93296     setFieldDefaults: function(defaults) {
93297         var me = this;
93298         Ext.iterate(defaults, function(key, val) {
93299             if (!me.hasOwnProperty(key)) {
93300                 me[key] = val;
93301             }
93302         });
93303     },
93304
93305     /**
93306      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
93307      * Note for implementors: if at all possible this method should be overridden with a custom implementation
93308      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
93309      */
93310     getBodyNaturalWidth: function() {
93311         return this.bodyEl.getWidth();
93312     }
93313
93314 });
93315
93316 /**
93317  * @docauthor Jason Johnston <jason@sencha.com>
93318  *
93319  * This mixin provides a common interface for the logical behavior and state of form fields, including:
93320  *
93321  * - Getter and setter methods for field values
93322  * - Events and methods for tracking value and validity changes
93323  * - Methods for triggering validation
93324  *
93325  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
93326  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
93327  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
93328  * plus methods that bind the Field value getters and setters to the input field's value.
93329  *
93330  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
93331  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
93332  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
93333  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
93334  * is called during the component's initialization.
93335  */
93336 Ext.define('Ext.form.field.Field', {
93337     /**
93338      * @property {Boolean} isFormField
93339      * Flag denoting that this component is a Field. Always true.
93340      */
93341     isFormField : true,
93342
93343     /**
93344      * @cfg {Object} value
93345      * A value to initialize this field with.
93346      */
93347
93348     /**
93349      * @cfg {String} name
93350      * The name of the field. By default this is used as the parameter name when including the
93351      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
93352      * being included in the form submit, set {@link #submitValue} to false.
93353      */
93354
93355     /**
93356      * @cfg {Boolean} disabled
93357      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
93358      */
93359     disabled : false,
93360
93361     /**
93362      * @cfg {Boolean} submitValue
93363      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
93364      * not disabled.
93365      */
93366     submitValue: true,
93367
93368     /**
93369      * @cfg {Boolean} validateOnChange
93370      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
93371      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
93372      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
93373      * typing.
93374      *
93375      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
93376      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
93377      * manually.
93378      *
93379      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
93380      * detected.
93381      */
93382     validateOnChange: true,
93383
93384     /**
93385      * @private
93386      */
93387     suspendCheckChange: 0,
93388
93389     /**
93390      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
93391      * their own initialization process.
93392      */
93393     initField: function() {
93394         this.addEvents(
93395             /**
93396              * @event change
93397              * Fires when a user-initiated change is detected in the value of the field.
93398              * @param {Ext.form.field.Field} this
93399              * @param {Object} newValue The new value
93400              * @param {Object} oldValue The original value
93401              */
93402             'change',
93403             /**
93404              * @event validitychange
93405              * Fires when a change in the field's validity is detected.
93406              * @param {Ext.form.field.Field} this
93407              * @param {Boolean} isValid Whether or not the field is now valid
93408              */
93409             'validitychange',
93410             /**
93411              * @event dirtychange
93412              * Fires when a change in the field's {@link #isDirty} state is detected.
93413              * @param {Ext.form.field.Field} this
93414              * @param {Boolean} isDirty Whether or not the field is now dirty
93415              */
93416             'dirtychange'
93417         );
93418
93419         this.initValue();
93420     },
93421
93422     /**
93423      * Initializes the field's value based on the initial config.
93424      */
93425     initValue: function() {
93426         var me = this;
93427
93428         /**
93429          * @property {Object} originalValue
93430          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
93431          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
93432          */
93433         me.originalValue = me.lastValue = me.value;
93434
93435         // Set the initial value - prevent validation on initial set
93436         me.suspendCheckChange++;
93437         me.setValue(me.value);
93438         me.suspendCheckChange--;
93439     },
93440
93441     /**
93442      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
93443      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
93444      * @return {String} name The field {@link Ext.form.field.Field#name name}
93445      */
93446     getName: function() {
93447         return this.name;
93448     },
93449
93450     /**
93451      * Returns the current data value of the field. The type of value returned is particular to the type of the
93452      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
93453      * @return {Object} value The field value
93454      */
93455     getValue: function() {
93456         return this.value;
93457     },
93458
93459     /**
93460      * Sets a data value into the field and runs the change detection and validation.
93461      * @param {Object} value The value to set
93462      * @return {Ext.form.field.Field} this
93463      */
93464     setValue: function(value) {
93465         var me = this;
93466         me.value = value;
93467         me.checkChange();
93468         return me;
93469     },
93470
93471     /**
93472      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
93473      * to provide custom comparison logic appropriate for the particular field's data type.
93474      * @param {Object} value1 The first value to compare
93475      * @param {Object} value2 The second value to compare
93476      * @return {Boolean} True if the values are equal, false if inequal.
93477      */
93478     isEqual: function(value1, value2) {
93479         return String(value1) === String(value2);
93480     },
93481     
93482     /**
93483      * Returns whether two values are logically equal.
93484      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
93485      * @private
93486      * @param {Object} value1 The first value to compare
93487      * @param {Object} value2 The second value to compare
93488      * @return {Boolean} True if the values are equal, false if inequal.
93489      */
93490     isEqualAsString: function(value1, value2){
93491         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
93492     },
93493
93494     /**
93495      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
93496      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
93497      * its current stringified value. More advanced field implementations may return more than one name-value pair.
93498      *
93499      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93500      * validated}.
93501      *
93502      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93503      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93504      * submitted.
93505      */
93506     getSubmitData: function() {
93507         var me = this,
93508             data = null;
93509         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
93510             data = {};
93511             data[me.getName()] = '' + me.getValue();
93512         }
93513         return data;
93514     },
93515
93516     /**
93517      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
93518      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
93519      * being this field's {@link #getName name} and the value being its current data value. More advanced field
93520      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
93521      * field names in the Model.
93522      *
93523      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93524      * validated}.
93525      *
93526      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93527      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93528      * submitted.
93529      */
93530     getModelData: function() {
93531         var me = this,
93532             data = null;
93533         if (!me.disabled && !me.isFileUpload()) {
93534             data = {};
93535             data[me.getName()] = me.getValue();
93536         }
93537         return data;
93538     },
93539
93540     /**
93541      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
93542      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
93543      */
93544     reset : function(){
93545         var me = this;
93546
93547         me.setValue(me.originalValue);
93548         me.clearInvalid();
93549         // delete here so we reset back to the original state
93550         delete me.wasValid;
93551     },
93552
93553     /**
93554      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
93555      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
93556      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
93557      */
93558     resetOriginalValue: function() {
93559         this.originalValue = this.getValue();
93560         this.checkDirty();
93561     },
93562
93563     /**
93564      * Checks whether the value of the field has changed since the last time it was checked.
93565      * If the value has changed, it:
93566      *
93567      * 1. Fires the {@link #change change event},
93568      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
93569      *    {@link #validitychange validitychange event} if the validity has changed, and
93570      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
93571      *    if it has changed.
93572      */
93573     checkChange: function() {
93574         if (!this.suspendCheckChange) {
93575             var me = this,
93576                 newVal = me.getValue(),
93577                 oldVal = me.lastValue;
93578             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
93579                 me.lastValue = newVal;
93580                 me.fireEvent('change', me, newVal, oldVal);
93581                 me.onChange(newVal, oldVal);
93582             }
93583         }
93584     },
93585
93586     /**
93587      * @private
93588      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
93589      * config is enabled, and invokes the dirty check.
93590      */
93591     onChange: function(newVal, oldVal) {
93592         if (this.validateOnChange) {
93593             this.validate();
93594         }
93595         this.checkDirty();
93596     },
93597
93598     /**
93599      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
93600      * Will always return false if the field is disabled.
93601      *
93602      * Note that if the owning {@link Ext.form.Basic form} was configured with
93603      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
93604      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
93605      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
93606      * false otherwise.
93607      */
93608     isDirty : function() {
93609         var me = this;
93610         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
93611     },
93612
93613     /**
93614      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
93615      * fires the {@link #dirtychange} event.
93616      */
93617     checkDirty: function() {
93618         var me = this,
93619             isDirty = me.isDirty();
93620         if (isDirty !== me.wasDirty) {
93621             me.fireEvent('dirtychange', me, isDirty);
93622             me.onDirtyChange(isDirty);
93623             me.wasDirty = isDirty;
93624         }
93625     },
93626
93627     /**
93628      * @private Called when the field's dirty state changes.
93629      * @param {Boolean} isDirty
93630      */
93631     onDirtyChange: Ext.emptyFn,
93632
93633     /**
93634      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
93635      * internally during validation and would not usually need to be used manually.
93636      *
93637      * Each subclass should override or augment the return value to provide their own errors.
93638      *
93639      * @param {Object} value The value to get errors for (defaults to the current field value)
93640      * @return {String[]} All error messages for this field; an empty Array if none.
93641      */
93642     getErrors: function(value) {
93643         return [];
93644     },
93645
93646     /**
93647      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93648      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
93649      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
93650      *
93651      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
93652      * message display.
93653      *
93654      * @return {Boolean} True if the value is valid, else false
93655      */
93656     isValid : function() {
93657         var me = this;
93658         return me.disabled || Ext.isEmpty(me.getErrors());
93659     },
93660
93661     /**
93662      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93663      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
93664      * **Note**: {@link #disabled} fields are always treated as valid.
93665      *
93666      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
93667      * To validate without side-effects, use {@link #isValid}.
93668      *
93669      * @return {Boolean} True if the value is valid, else false
93670      */
93671     validate : function() {
93672         var me = this,
93673             isValid = me.isValid();
93674         if (isValid !== me.wasValid) {
93675             me.wasValid = isValid;
93676             me.fireEvent('validitychange', me, isValid);
93677         }
93678         return isValid;
93679     },
93680
93681     /**
93682      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
93683      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
93684      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
93685      * change.
93686      * @param {Object} fn A function containing the transaction code
93687      */
93688     batchChanges: function(fn) {
93689         try {
93690             this.suspendCheckChange++;
93691             fn();
93692         } catch(e){
93693             throw e;
93694         } finally {
93695             this.suspendCheckChange--;
93696         }
93697         this.checkChange();
93698     },
93699
93700     /**
93701      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
93702      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
93703      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
93704      * input element.
93705      * @return {Boolean}
93706      */
93707     isFileUpload: function() {
93708         return false;
93709     },
93710
93711     /**
93712      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
93713      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
93714      * returned, so this method should also create a replacement.
93715      * @return {HTMLElement}
93716      */
93717     extractFileInput: function() {
93718         return null;
93719     },
93720
93721     /**
93722      * @method markInvalid
93723      * Associate one or more error messages with this field. Components using this mixin should implement this method to
93724      * update the component's rendering to display the messages.
93725      *
93726      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
93727      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
93728      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93729      *
93730      * @param {String/String[]} errors The error message(s) for the field.
93731      */
93732     markInvalid: Ext.emptyFn,
93733
93734     /**
93735      * @method clearInvalid
93736      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
93737      * update the components rendering to clear any existing messages.
93738      *
93739      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
93740      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
93741      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93742      */
93743     clearInvalid: Ext.emptyFn
93744
93745 });
93746
93747 /**
93748  * @class Ext.layout.component.field.Field
93749  * @extends Ext.layout.component.Component
93750  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
93751  * the form control, label, and error message treatment.
93752  * @private
93753  */
93754 Ext.define('Ext.layout.component.field.Field', {
93755
93756     /* Begin Definitions */
93757
93758     alias: ['layout.field'],
93759
93760     extend: 'Ext.layout.component.Component',
93761
93762     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
93763
93764     /* End Definitions */
93765
93766     type: 'field',
93767
93768     beforeLayout: function(width, height) {
93769         var me = this;
93770         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
93771     },
93772
93773     onLayout: function(width, height) {
93774         var me = this,
93775             owner = me.owner,
93776             labelStrategy = me.getLabelStrategy(),
93777             errorStrategy = me.getErrorStrategy(),
93778             isDefined = Ext.isDefined,
93779             isNumber = Ext.isNumber,
93780             lastSize, autoWidth, autoHeight, info, undef;
93781
93782         lastSize = me.lastComponentSize || {};
93783         if (!isDefined(width)) {
93784             width = lastSize.width;
93785             if (width < 0) { //first pass lastComponentSize.width is -Infinity
93786                 width = undef;
93787             }
93788         }
93789         if (!isDefined(height)) {
93790             height = lastSize.height;
93791             if (height < 0) { //first pass lastComponentSize.height is -Infinity
93792                 height = undef;
93793             }
93794         }
93795         autoWidth = !isNumber(width);
93796         autoHeight = !isNumber(height);
93797
93798         info = {
93799             autoWidth: autoWidth,
93800             autoHeight: autoHeight,
93801             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
93802             height: height,
93803             setOuterWidth: false, //whether the outer el width should be set to the calculated width
93804
93805             // insets for the bodyEl from each side of the component layout area
93806             insets: {
93807                 top: 0,
93808                 right: 0,
93809                 bottom: 0,
93810                 left: 0
93811             }
93812         };
93813
93814         // NOTE the order of calculating insets and setting styles here is very important; we must first
93815         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
93816         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
93817
93818         // perform preparation on the label and error (setting css classes, qtips, etc.)
93819         labelStrategy.prepare(owner, info);
93820         errorStrategy.prepare(owner, info);
93821
93822         // calculate the horizontal insets for the label and error
93823         labelStrategy.adjustHorizInsets(owner, info);
93824         errorStrategy.adjustHorizInsets(owner, info);
93825
93826         // set horizontal styles for label and error based on the current insets
93827         labelStrategy.layoutHoriz(owner, info);
93828         errorStrategy.layoutHoriz(owner, info);
93829
93830         // calculate the vertical insets for the label and error
93831         labelStrategy.adjustVertInsets(owner, info);
93832         errorStrategy.adjustVertInsets(owner, info);
93833
93834         // set vertical styles for label and error based on the current insets
93835         labelStrategy.layoutVert(owner, info);
93836         errorStrategy.layoutVert(owner, info);
93837
93838         // perform sizing of the elements based on the final dimensions and insets
93839         if (autoWidth && autoHeight) {
93840             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
93841             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
93842         } else {
93843             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
93844         }
93845         me.sizeBody(info);
93846
93847         me.activeError = owner.getActiveError();
93848     },
93849     
93850     onFocus: function(){
93851         this.getErrorStrategy().onFocus(this.owner);    
93852     },
93853
93854
93855     /**
93856      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
93857      */
93858     sizeBody: function(info) {
93859         var me = this,
93860             owner = me.owner,
93861             insets = info.insets,
93862             totalWidth = info.width,
93863             totalHeight = info.height,
93864             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
93865             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
93866
93867         // size the bodyEl
93868         me.setElementSize(owner.bodyEl, width, height);
93869
93870         // size the bodyEl's inner contents if necessary
93871         me.sizeBodyContents(width, height);
93872     },
93873
93874     /**
93875      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
93876      * default, subclasses can override to handle their specific contents.
93877      * @param {Number} width The bodyEl width
93878      * @param {Number} height The bodyEl height
93879      */
93880     sizeBodyContents: Ext.emptyFn,
93881
93882
93883     /**
93884      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
93885      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
93886      */
93887     getLabelStrategy: function() {
93888         var me = this,
93889             strategies = me.labelStrategies,
93890             labelAlign = me.owner.labelAlign;
93891         return strategies[labelAlign] || strategies.base;
93892     },
93893
93894     /**
93895      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
93896      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
93897      */
93898     getErrorStrategy: function() {
93899         var me = this,
93900             owner = me.owner,
93901             strategies = me.errorStrategies,
93902             msgTarget = owner.msgTarget;
93903         return !owner.preventMark && Ext.isString(msgTarget) ?
93904                 (strategies[msgTarget] || strategies.elementId) :
93905                 strategies.none;
93906     },
93907
93908
93909
93910     /**
93911      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
93912      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
93913      */
93914     labelStrategies: (function() {
93915         var applyIf = Ext.applyIf,
93916             emptyFn = Ext.emptyFn,
93917             base = {
93918                 prepare: function(owner, info) {
93919                     var cls = owner.labelCls + '-' + owner.labelAlign,
93920                         labelEl = owner.labelEl;
93921                     if (labelEl && !labelEl.hasCls(cls)) {
93922                         labelEl.addCls(cls);
93923                     }
93924                 },
93925                 adjustHorizInsets: emptyFn,
93926                 adjustVertInsets: emptyFn,
93927                 layoutHoriz: emptyFn,
93928                 layoutVert: emptyFn
93929             },
93930             left = applyIf({
93931                 prepare: function(owner, info) {
93932                     base.prepare(owner, info);
93933                     // If auto width, add the label width to the body's natural width.
93934                     if (info.autoWidth) {
93935                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
93936                     }
93937                     // Must set outer width to prevent field from wrapping below floated label
93938                     info.setOuterWidth = true;
93939                 },
93940                 adjustHorizInsets: function(owner, info) {
93941                     if (owner.labelEl) {
93942                         info.insets.left += owner.labelWidth + owner.labelPad;
93943                     }
93944                 },
93945                 layoutHoriz: function(owner, info) {
93946                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
93947                     // setting the width style because it needs to account for the final calculated
93948                     // padding/border styles for the label. So we set the width programmatically here to
93949                     // normalize content-box sizing, while letting border-box browsers use the original
93950                     // width style.
93951                     var labelEl = owner.labelEl;
93952                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
93953                         labelEl.setWidth(owner.labelWidth);
93954                         owner.isLabelSized = true;
93955                     }
93956                 }
93957             }, base);
93958
93959
93960         return {
93961             base: base,
93962
93963             /**
93964              * Label displayed above the bodyEl
93965              */
93966             top: applyIf({
93967                 adjustVertInsets: function(owner, info) {
93968                     var labelEl = owner.labelEl;
93969                     if (labelEl) {
93970                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
93971                                            labelEl.getFrameWidth('tb') + owner.labelPad;
93972                     }
93973                 }
93974             }, base),
93975
93976             /**
93977              * Label displayed to the left of the bodyEl
93978              */
93979             left: left,
93980
93981             /**
93982              * Same as left, only difference is text-align in CSS
93983              */
93984             right: left
93985         };
93986     })(),
93987
93988
93989
93990     /**
93991      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
93992      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
93993      */
93994     errorStrategies: (function() {
93995         function setDisplayed(el, displayed) {
93996             var wasDisplayed = el.getStyle('display') !== 'none';
93997             if (displayed !== wasDisplayed) {
93998                 el.setDisplayed(displayed);
93999             }
94000         }
94001
94002         function setStyle(el, name, value) {
94003             if (el.getStyle(name) !== value) {
94004                 el.setStyle(name, value);
94005             }
94006         }
94007         
94008         function showTip(owner) {
94009             var tip = Ext.layout.component.field.Field.tip,
94010                 target;
94011                 
94012             if (tip && tip.isVisible()) {
94013                 target = tip.activeTarget;
94014                 if (target && target.el === owner.getActionEl().dom) {
94015                     tip.toFront(true);
94016                 }
94017             }
94018         }
94019
94020         var applyIf = Ext.applyIf,
94021             emptyFn = Ext.emptyFn,
94022             base = {
94023                 prepare: function(owner) {
94024                     setDisplayed(owner.errorEl, false);
94025                 },
94026                 adjustHorizInsets: emptyFn,
94027                 adjustVertInsets: emptyFn,
94028                 layoutHoriz: emptyFn,
94029                 layoutVert: emptyFn,
94030                 onFocus: emptyFn
94031             };
94032
94033         return {
94034             none: base,
94035
94036             /**
94037              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
94038              */
94039             side: applyIf({
94040                 prepare: function(owner) {
94041                     var errorEl = owner.errorEl;
94042                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
94043                     Ext.layout.component.field.Field.initTip();
94044                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94045                     setDisplayed(errorEl, owner.hasActiveError());
94046                 },
94047                 adjustHorizInsets: function(owner, info) {
94048                     if (owner.autoFitErrors && owner.hasActiveError()) {
94049                         info.insets.right += owner.errorEl.getWidth();
94050                     }
94051                 },
94052                 layoutHoriz: function(owner, info) {
94053                     if (owner.hasActiveError()) {
94054                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
94055                     }
94056                 },
94057                 layoutVert: function(owner, info) {
94058                     if (owner.hasActiveError()) {
94059                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
94060                     }
94061                 },
94062                 onFocus: showTip
94063             }, base),
94064
94065             /**
94066              * Error message displayed underneath the bodyEl
94067              */
94068             under: applyIf({
94069                 prepare: function(owner) {
94070                     var errorEl = owner.errorEl,
94071                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
94072                     if (!errorEl.hasCls(cls)) {
94073                         errorEl.addCls(cls);
94074                     }
94075                     setDisplayed(errorEl, owner.hasActiveError());
94076                 },
94077                 adjustVertInsets: function(owner, info) {
94078                     if (owner.autoFitErrors) {
94079                         info.insets.bottom += owner.errorEl.getHeight();
94080                     }
94081                 },
94082                 layoutHoriz: function(owner, info) {
94083                     var errorEl = owner.errorEl,
94084                         insets = info.insets;
94085
94086                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
94087                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
94088                 }
94089             }, base),
94090
94091             /**
94092              * Error displayed as QuickTip on hover of the field container
94093              */
94094             qtip: applyIf({
94095                 prepare: function(owner) {
94096                     setDisplayed(owner.errorEl, false);
94097                     Ext.layout.component.field.Field.initTip();
94098                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94099                 },
94100                 onFocus: showTip
94101             }, base),
94102
94103             /**
94104              * Error displayed as title tip on hover of the field container
94105              */
94106             title: applyIf({
94107                 prepare: function(owner) {
94108                     setDisplayed(owner.errorEl, false);
94109                     owner.el.dom.title = owner.getActiveError() || '';
94110                 }
94111             }, base),
94112
94113             /**
94114              * Error message displayed as content of an element with a given id elsewhere in the app
94115              */
94116             elementId: applyIf({
94117                 prepare: function(owner) {
94118                     setDisplayed(owner.errorEl, false);
94119                     var targetEl = Ext.fly(owner.msgTarget);
94120                     if (targetEl) {
94121                         targetEl.dom.innerHTML = owner.getActiveError() || '';
94122                         targetEl.setDisplayed(owner.hasActiveError());
94123                     }
94124                 }
94125             }, base)
94126         };
94127     })(),
94128
94129     statics: {
94130         /**
94131          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
94132          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
94133          */
94134         initTip: function() {
94135             var tip = this.tip;
94136             if (!tip) {
94137                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
94138                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
94139                     renderTo: Ext.getBody()
94140                 });
94141                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
94142             }
94143         },
94144
94145         /**
94146          * Destroy the error tip instance.
94147          */
94148         destroyTip: function() {
94149             var tip = this.tip;
94150             if (tip) {
94151                 tip.destroy();
94152                 delete this.tip;
94153             }
94154         }
94155     }
94156
94157 });
94158
94159 /**
94160  * @singleton
94161  * @alternateClassName Ext.form.VTypes
94162  *
94163  * This is a singleton object which contains a set of commonly used field validation functions
94164  * and provides a mechanism for creating reusable custom field validations.
94165  * The following field validation functions are provided out of the box:
94166  *
94167  * - {@link #alpha}
94168  * - {@link #alphanum}
94169  * - {@link #email}
94170  * - {@link #url}
94171  *
94172  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
94173  *
94174  *     Ext.create('Ext.form.field.Text', {
94175  *         fieldLabel: 'Email Address',
94176  *         name: 'email',
94177  *         vtype: 'email' // applies email validation rules to this field
94178  *     });
94179  *
94180  * To create custom VTypes:
94181  *
94182  *     // custom Vtype for vtype:'time'
94183  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
94184  *     Ext.apply(Ext.form.field.VTypes, {
94185  *         //  vtype validation function
94186  *         time: function(val, field) {
94187  *             return timeTest.test(val);
94188  *         },
94189  *         // vtype Text property: The error text to display when the validation function returns false
94190  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
94191  *         // vtype Mask property: The keystroke filter mask
94192  *         timeMask: /[\d\s:amp]/i
94193  *     });
94194  *
94195  * In the above example the `time` function is the validator that will run when field validation occurs,
94196  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
94197  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
94198  *
94199  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
94200  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
94201  *
94202  *     Ext.create('Ext.form.field.Text', {
94203  *         fieldLabel: 'Departure Time',
94204  *         name: 'departureTime',
94205  *         vtype: 'time' // applies custom time validation rules to this field
94206  *     });
94207  *
94208  * Another example of a custom validator:
94209  *
94210  *     // custom Vtype for vtype:'IPAddress'
94211  *     Ext.apply(Ext.form.field.VTypes, {
94212  *         IPAddress:  function(v) {
94213  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
94214  *         },
94215  *         IPAddressText: 'Must be a numeric IP address',
94216  *         IPAddressMask: /[\d\.]/i
94217  *     });
94218  *
94219  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
94220  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
94221  */
94222 Ext.define('Ext.form.field.VTypes', (function(){
94223     // closure these in so they are only created once.
94224     var alpha = /^[a-zA-Z_]+$/,
94225         alphanum = /^[a-zA-Z0-9_]+$/,
94226         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
94227         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
94228
94229     // All these messages and functions are configurable
94230     return {
94231         singleton: true,
94232         alternateClassName: 'Ext.form.VTypes',
94233
94234         /**
94235          * The function used to validate email addresses. Note that this is a very basic validation - complete
94236          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
94237          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
94238          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
94239          * to validate the following emails:
94240          *
94241          * - `barney@example.de`
94242          * - `barney.rubble@example.com`
94243          * - `barney-rubble@example.coop`
94244          * - `barney+rubble@example.com`
94245          *
94246          * [1]: http://en.wikipedia.org/wiki/E-mail_address
94247          *
94248          * @param {String} value The email address
94249          * @return {Boolean} true if the RegExp test passed, and false if not.
94250          */
94251         'email' : function(v){
94252             return email.test(v);
94253         },
94254         /**
94255          * @property {String} emailText
94256          * The error text to display when the email validation function returns false.
94257          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
94258          */
94259         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
94260         /**
94261          * @property {RegExp} emailMask
94262          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
94263          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
94264          */
94265         'emailMask' : /[a-z0-9_\.\-@\+]/i,
94266
94267         /**
94268          * The function used to validate URLs
94269          * @param {String} value The URL
94270          * @return {Boolean} true if the RegExp test passed, and false if not.
94271          */
94272         'url' : function(v){
94273             return url.test(v);
94274         },
94275         /**
94276          * @property {String} urlText
94277          * The error text to display when the url validation function returns false.
94278          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
94279          */
94280         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
94281
94282         /**
94283          * The function used to validate alpha values
94284          * @param {String} value The value
94285          * @return {Boolean} true if the RegExp test passed, and false if not.
94286          */
94287         'alpha' : function(v){
94288             return alpha.test(v);
94289         },
94290         /**
94291          * @property {String} alphaText
94292          * The error text to display when the alpha validation function returns false.
94293          * Defaults to: 'This field should only contain letters and _'
94294          */
94295         'alphaText' : 'This field should only contain letters and _',
94296         /**
94297          * @property {RegExp} alphaMask
94298          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
94299          */
94300         'alphaMask' : /[a-z_]/i,
94301
94302         /**
94303          * The function used to validate alphanumeric values
94304          * @param {String} value The value
94305          * @return {Boolean} true if the RegExp test passed, and false if not.
94306          */
94307         'alphanum' : function(v){
94308             return alphanum.test(v);
94309         },
94310         /**
94311          * @property {String} alphanumText
94312          * The error text to display when the alphanumeric validation function returns false.
94313          * Defaults to: 'This field should only contain letters, numbers and _'
94314          */
94315         'alphanumText' : 'This field should only contain letters, numbers and _',
94316         /**
94317          * @property {RegExp} alphanumMask
94318          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
94319          */
94320         'alphanumMask' : /[a-z0-9_]/i
94321     };
94322 })());
94323
94324 /**
94325  * @private
94326  * @class Ext.layout.component.field.Text
94327  * @extends Ext.layout.component.field.Field
94328  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
94329  */
94330 Ext.define('Ext.layout.component.field.Text', {
94331     extend: 'Ext.layout.component.field.Field',
94332     alias: 'layout.textfield',
94333     requires: ['Ext.util.TextMetrics'],
94334
94335     type: 'textfield',
94336
94337
94338     /**
94339      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
94340      * changed since the last layout.
94341      */
94342     beforeLayout: function(width, height) {
94343         var me = this,
94344             owner = me.owner,
94345             lastValue = this.lastValue,
94346             value = owner.getRawValue();
94347         this.lastValue = value;
94348         return me.callParent(arguments) || (owner.grow && value !== lastValue);
94349     },
94350
94351
94352     /**
94353      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
94354      * {@link Ext.form.field.Text#grow} configurations.
94355      * @param {Number} width The bodyEl width
94356      * @param {Number} height The bodyEl height
94357      */
94358     sizeBodyContents: function(width, height) {
94359         var size = this.adjustForGrow(width, height);
94360         this.setElementSize(this.owner.inputEl, size[0], size[1]);
94361     },
94362
94363
94364     /**
94365      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94366      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
94367      * @param {Number} width The bodyEl width
94368      * @param {Number} height The bodyEl height
94369      * @return {Number[]} [inputElWidth, inputElHeight]
94370      */
94371     adjustForGrow: function(width, height) {
94372         var me = this,
94373             owner = me.owner,
94374             inputEl, value, calcWidth,
94375             result = [width, height];
94376
94377         if (owner.grow) {
94378             inputEl = owner.inputEl;
94379
94380             // Find the width that contains the whole text value
94381             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
94382             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
94383
94384             // Constrain
94385             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
94386                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
94387         }
94388
94389         return result;
94390     }
94391
94392 });
94393
94394 /**
94395  * @private
94396  * @class Ext.layout.component.field.TextArea
94397  * @extends Ext.layout.component.field.Field
94398  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
94399  */
94400 Ext.define('Ext.layout.component.field.TextArea', {
94401     extend: 'Ext.layout.component.field.Text',
94402     alias: 'layout.textareafield',
94403
94404     type: 'textareafield',
94405
94406
94407     /**
94408      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94409      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
94410      * textfield layout's implementation to handle height rather than width.
94411      * @param {Number} width The bodyEl width
94412      * @param {Number} height The bodyEl height
94413      * @return {Number[]} [inputElWidth, inputElHeight]
94414      */
94415     adjustForGrow: function(width, height) {
94416         var me = this,
94417             owner = me.owner,
94418             inputEl, value, max,
94419             curWidth, curHeight, calcHeight,
94420             result = [width, height];
94421
94422         if (owner.grow) {
94423             inputEl = owner.inputEl;
94424             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
94425             curHeight = inputEl.getHeight();
94426
94427             // Get and normalize the field value for measurement
94428             value = inputEl.dom.value || '&#160;';
94429             value += owner.growAppend;
94430
94431             // Translate newlines to <br> tags
94432             value = value.replace(/\n/g, '<br>');
94433
94434             // Find the height that contains the whole text value
94435             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
94436                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
94437
94438             // Constrain
94439             max = owner.growMax;
94440             if (Ext.isNumber(height)) {
94441                 max = Math.min(max, height);
94442             }
94443             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
94444         }
94445
94446         return result;
94447     }
94448
94449 });
94450 /**
94451  * @class Ext.layout.container.Anchor
94452  * @extends Ext.layout.container.Container
94453  * 
94454  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
94455  * If the container is resized, all anchored items are automatically rerendered according to their
94456  * `{@link #anchor}` rules.
94457  *
94458  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
94459  * config, and should generally not need to be created directly via the new keyword.
94460  * 
94461  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
94462  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
94463  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
94464  *
94465  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
94466  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
94467  * logic if necessary.  
94468  *
94469  *     @example
94470  *     Ext.create('Ext.Panel', {
94471  *         width: 500,
94472  *         height: 400,
94473  *         title: "AnchorLayout Panel",
94474  *         layout: 'anchor',
94475  *         renderTo: Ext.getBody(),
94476  *         items: [
94477  *             {
94478  *                 xtype: 'panel',
94479  *                 title: '75% Width and 20% Height',
94480  *                 anchor: '75% 20%'
94481  *             },
94482  *             {
94483  *                 xtype: 'panel',
94484  *                 title: 'Offset -300 Width & -200 Height',
94485  *                 anchor: '-300 -200'          
94486  *             },
94487  *             {
94488  *                 xtype: 'panel',
94489  *                 title: 'Mixed Offset and Percent',
94490  *                 anchor: '-250 20%'
94491  *             }
94492  *         ]
94493  *     });
94494  */
94495 Ext.define('Ext.layout.container.Anchor', {
94496
94497     /* Begin Definitions */
94498
94499     alias: 'layout.anchor',
94500     extend: 'Ext.layout.container.Container',
94501     alternateClassName: 'Ext.layout.AnchorLayout',
94502
94503     /* End Definitions */
94504
94505     /**
94506      * @cfg {String} anchor
94507      *
94508      * This configuation option is to be applied to **child `items`** of a container managed by
94509      * this layout (ie. configured with `layout:'anchor'`).
94510      *
94511      * This value is what tells the layout how an item should be anchored to the container. `items`
94512      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
94513      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
94514      * The following types of anchor values are supported:
94515      *
94516      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
94517      *
94518      *   The first anchor is the percentage width that the item should take up within the container, and the
94519      *   second is the percentage height.  For example:
94520      *
94521      *       // two values specified
94522      *       anchor: '100% 50%' // render item complete width of the container and
94523      *                          // 1/2 height of the container
94524      *       // one value specified
94525      *       anchor: '100%'     // the width value; the height will default to auto
94526      *
94527      * - **Offsets** : Any positive or negative integer value.
94528      *
94529      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
94530      *   and the second is the offset from the bottom edge. For example:
94531      *
94532      *       // two values specified
94533      *       anchor: '-50 -100' // render item the complete width of the container
94534      *                          // minus 50 pixels and
94535      *                          // the complete height minus 100 pixels.
94536      *       // one value specified
94537      *       anchor: '-50'      // anchor value is assumed to be the right offset value
94538      *                          // bottom offset will default to 0
94539      *
94540      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
94541      *
94542      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
94543      *   order for these to have any effect.
94544      *   
94545      * - **Mixed** :
94546      *
94547      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
94548      *   right edge by 50 pixels and 75% of the container's height use:
94549      *   
94550      *       anchor:   '-50 75%'
94551      */
94552     type: 'anchor',
94553
94554     /**
94555      * @cfg {String} defaultAnchor
94556      * 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%'.
94557      */
94558     defaultAnchor: '100%',
94559
94560     parseAnchorRE: /^(r|right|b|bottom)$/i,
94561
94562     // private
94563     onLayout: function() {
94564         this.callParent(arguments);
94565
94566         var me = this,
94567             size = me.getLayoutTargetSize(),
94568             owner = me.owner,
94569             target = me.getTarget(),
94570             ownerWidth = size.width,
94571             ownerHeight = size.height,
94572             overflow = target.getStyle('overflow'),
94573             components = me.getVisibleItems(owner),
94574             len = components.length,
94575             boxes = [],
94576             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
94577             i, el, cleaner;
94578
94579         if (ownerWidth < 20 && ownerHeight < 20) {
94580             return;
94581         }
94582
94583         // Anchor layout uses natural HTML flow to arrange the child items.
94584         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
94585         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
94586         if (!me.clearEl) {
94587             me.clearEl = target.createChild({
94588                 cls: Ext.baseCSSPrefix + 'clear',
94589                 role: 'presentation'
94590             });
94591         }
94592
94593         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94594         if (!Ext.supports.RightMargin) {
94595             cleaner = Ext.Element.getRightMarginFixCleaner(target);
94596             target.addCls(Ext.baseCSSPrefix + 'inline-children');
94597         }
94598
94599         for (i = 0; i < len; i++) {
94600             component = components[i];
94601             el = component.el;
94602
94603             anchorSpec = component.anchorSpec;
94604             if (anchorSpec) {
94605                 if (anchorSpec.right) {
94606                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
94607                 } else {
94608                     calcWidth = undefined;
94609                 }
94610                 if (anchorSpec.bottom) {
94611                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
94612                 } else {
94613                     calcHeight = undefined;
94614                 }
94615
94616                 boxes.push({
94617                     component: component,
94618                     anchor: true,
94619                     width: calcWidth || undefined,
94620                     height: calcHeight || undefined
94621                 });
94622             } else {
94623                 boxes.push({
94624                     component: component,
94625                     anchor: false
94626                 });
94627             }
94628         }
94629
94630         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94631         if (!Ext.supports.RightMargin) {
94632             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
94633             cleaner();
94634         }
94635
94636         for (i = 0; i < len; i++) {
94637             box = boxes[i];
94638             me.setItemSize(box.component, box.width, box.height);
94639         }
94640
94641         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
94642             newTargetSize = me.getLayoutTargetSize();
94643             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
94644                 me.adjustmentPass = true;
94645                 me.onLayout();
94646             }
94647         }
94648
94649         delete me.adjustmentPass;
94650     },
94651
94652     // private
94653     parseAnchor: function(a, start, cstart) {
94654         if (a && a != 'none') {
94655             var ratio;
94656             // standard anchor
94657             if (this.parseAnchorRE.test(a)) {
94658                 var diff = cstart - start;
94659                 return function(v) {
94660                     return v - diff;
94661                 };
94662             }    
94663             // percentage
94664             else if (a.indexOf('%') != -1) {
94665                 ratio = parseFloat(a.replace('%', '')) * 0.01;
94666                 return function(v) {
94667                     return Math.floor(v * ratio);
94668                 };
94669             }    
94670             // simple offset adjustment
94671             else {
94672                 a = parseInt(a, 10);
94673                 if (!isNaN(a)) {
94674                     return function(v) {
94675                         return v + a;
94676                     };
94677                 }
94678             }
94679         }
94680         return null;
94681     },
94682
94683     // private
94684     adjustWidthAnchor: function(value, comp) {
94685         return value;
94686     },
94687
94688     // private
94689     adjustHeightAnchor: function(value, comp) {
94690         return value;
94691     },
94692
94693     configureItem: function(item) {
94694         var me = this,
94695             owner = me.owner,
94696             anchor= item.anchor,
94697             anchorsArray,
94698             anchorSpec,
94699             anchorWidth,
94700             anchorHeight;
94701
94702         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
94703             item.anchor = anchor = me.defaultAnchor;
94704         }
94705
94706         // find the container anchoring size
94707         if (owner.anchorSize) {
94708             if (typeof owner.anchorSize == 'number') {
94709                 anchorWidth = owner.anchorSize;
94710             }
94711             else {
94712                 anchorWidth = owner.anchorSize.width;
94713                 anchorHeight = owner.anchorSize.height;
94714             }
94715         }
94716         else {
94717             anchorWidth = owner.initialConfig.width;
94718             anchorHeight = owner.initialConfig.height;
94719         }
94720
94721         if (anchor) {
94722             // cache all anchor values
94723             anchorsArray = anchor.split(' ');
94724             item.anchorSpec = anchorSpec = {
94725                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
94726                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
94727             };
94728
94729             if (anchorSpec.right) {
94730                 item.layoutManagedWidth = 1;
94731             } else {
94732                 item.layoutManagedWidth = 2;
94733             }
94734
94735             if (anchorSpec.bottom) {
94736                 item.layoutManagedHeight = 1;
94737             } else {
94738                 item.layoutManagedHeight = 2;
94739             }
94740         } else {
94741             item.layoutManagedWidth = 2;
94742             item.layoutManagedHeight = 2;
94743         }
94744         this.callParent(arguments);
94745     }
94746
94747 });
94748 /**
94749  * @class Ext.form.action.Load
94750  * @extends Ext.form.action.Action
94751  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
94752  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
94753  * {@link Ext.form.Basic#load load}ing.</p>
94754  * <p><u><b>Response Packet Criteria</b></u></p>
94755  * <p>A response packet <b>must</b> contain:
94756  * <div class="mdetail-params"><ul>
94757  * <li><b><code>success</code></b> property : Boolean</li>
94758  * <li><b><code>data</code></b> property : Object</li>
94759  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
94760  * The individual value object for each Field is passed to the Field's
94761  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
94762  * </ul></div>
94763  * <p><u><b>JSON Packets</b></u></p>
94764  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
94765 var myFormPanel = new Ext.form.Panel({
94766     title: 'Client and routing info',
94767     items: [{
94768         fieldLabel: 'Client',
94769         name: 'clientName'
94770     }, {
94771         fieldLabel: 'Port of loading',
94772         name: 'portOfLoading'
94773     }, {
94774         fieldLabel: 'Port of discharge',
94775         name: 'portOfDischarge'
94776     }]
94777 });
94778 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
94779     url: '/getRoutingInfo.php',
94780     params: {
94781         consignmentRef: myConsignmentRef
94782     },
94783     failure: function(form, action) {
94784         Ext.Msg.alert("Load failed", action.result.errorMessage);
94785     }
94786 });
94787 </code></pre>
94788  * a <b>success response</b> packet may look like this:</p><pre><code>
94789 {
94790     success: true,
94791     data: {
94792         clientName: "Fred. Olsen Lines",
94793         portOfLoading: "FXT",
94794         portOfDischarge: "OSL"
94795     }
94796 }</code></pre>
94797  * while a <b>failure response</b> packet may look like this:</p><pre><code>
94798 {
94799     success: false,
94800     errorMessage: "Consignment reference not found"
94801 }</code></pre>
94802  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
94803  * callback or event handler methods. The object decoded from this JSON is available in the
94804  * {@link Ext.form.action.Action#result result} property.</p>
94805  */
94806 Ext.define('Ext.form.action.Load', {
94807     extend:'Ext.form.action.Action',
94808     requires: ['Ext.data.Connection'],
94809     alternateClassName: 'Ext.form.Action.Load',
94810     alias: 'formaction.load',
94811
94812     type: 'load',
94813
94814     /**
94815      * @private
94816      */
94817     run: function() {
94818         Ext.Ajax.request(Ext.apply(
94819             this.createCallback(),
94820             {
94821                 method: this.getMethod(),
94822                 url: this.getUrl(),
94823                 headers: this.headers,
94824                 params: this.getParams()
94825             }
94826         ));
94827     },
94828
94829     /**
94830      * @private
94831      */
94832     onSuccess: function(response){
94833         var result = this.processResponse(response),
94834             form = this.form;
94835         if (result === true || !result.success || !result.data) {
94836             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
94837             form.afterAction(this, false);
94838             return;
94839         }
94840         form.clearInvalid();
94841         form.setValues(result.data);
94842         form.afterAction(this, true);
94843     },
94844
94845     /**
94846      * @private
94847      */
94848     handleResponse: function(response) {
94849         var reader = this.form.reader,
94850             rs, data;
94851         if (reader) {
94852             rs = reader.read(response);
94853             data = rs.records && rs.records[0] ? rs.records[0].data : null;
94854             return {
94855                 success : rs.success,
94856                 data : data
94857             };
94858         }
94859         return Ext.decode(response.responseText);
94860     }
94861 });
94862
94863
94864 /**
94865  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
94866  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
94867  * their prior size, and can be {@link #minimize}d.
94868  *
94869  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
94870  * grouping, activation, to front, to back and other application-specific behavior.
94871  *
94872  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
94873  * {@link Ext.Component#renderTo renderTo}.
94874  *
94875  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
94876  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
94877  * in the required manner.**
94878  *
94879  *     @example
94880  *     Ext.create('Ext.window.Window', {
94881  *         title: 'Hello',
94882  *         height: 200,
94883  *         width: 400,
94884  *         layout: 'fit',
94885  *         items: {  // Let's put an empty grid in just to illustrate fit layout
94886  *             xtype: 'grid',
94887  *             border: false,
94888  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
94889  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
94890  *         }
94891  *     }).show();
94892  */
94893 Ext.define('Ext.window.Window', {
94894     extend: 'Ext.panel.Panel',
94895
94896     alternateClassName: 'Ext.Window',
94897
94898     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
94899
94900     alias: 'widget.window',
94901
94902     /**
94903      * @cfg {Number} x
94904      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
94905      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94906      */
94907
94908     /**
94909      * @cfg {Number} y
94910      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
94911      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94912      */
94913
94914     /**
94915      * @cfg {Boolean} [modal=false]
94916      * True to make the window modal and mask everything behind it when displayed, false to display it without
94917      * restricting access to other UI elements.
94918      */
94919
94920     /**
94921      * @cfg {String/Ext.Element} [animateTarget=null]
94922      * Id or element from which the window should animate while opening.
94923      */
94924
94925     /**
94926      * @cfg {String/Number/Ext.Component} defaultFocus
94927      * Specifies a Component to receive focus when this Window is focused.
94928      *
94929      * This may be one of:
94930      *
94931      *   - The index of a footer Button.
94932      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
94933      *   - A Component.
94934      */
94935
94936     /**
94937      * @cfg {Function} onEsc
94938      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
94939      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
94940      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
94941      */
94942
94943     /**
94944      * @cfg {Boolean} [collapsed=false]
94945      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
94946      * is true (the default) it will override the `collapsed` config and the window will always be
94947      * expanded when shown.
94948      */
94949
94950     /**
94951      * @cfg {Boolean} [maximized=false]
94952      * True to initially display the window in a maximized state.
94953      */
94954
94955     /**
94956     * @cfg {String} [baseCls='x-window']
94957     * The base CSS class to apply to this panel's element.
94958     */
94959     baseCls: Ext.baseCSSPrefix + 'window',
94960
94961     /**
94962      * @cfg {Boolean/Object} resizable
94963      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
94964      *
94965      * This may also be specified as a config object to Ext.resizer.Resizer
94966      */
94967     resizable: true,
94968
94969     /**
94970      * @cfg {Boolean} draggable
94971      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
94972      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
94973      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
94974      */
94975     draggable: true,
94976
94977     /**
94978      * @cfg {Boolean} constrain
94979      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
94980      * element. By default the window will be rendered to document.body. To render and constrain the window within
94981      * another element specify {@link #renderTo}. Optionally the header only can be constrained
94982      * using {@link #constrainHeader}.
94983      */
94984     constrain: false,
94985
94986     /**
94987      * @cfg {Boolean} constrainHeader
94988      * True to constrain the window header within its containing element (allowing the window body to fall outside of
94989      * its containing element) or false to allow the header to fall outside its containing element.
94990      * Optionally the entire window can be constrained using {@link #constrain}.
94991      */
94992     constrainHeader: false,
94993
94994     /**
94995      * @cfg {Boolean} plain
94996      * True to render the window body with a transparent background so that it will blend into the framing elements,
94997      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
94998      * from the surrounding frame.
94999      */
95000     plain: false,
95001
95002     /**
95003      * @cfg {Boolean} minimizable
95004      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
95005      * and disallow minimizing the window. Note that this button provides no implementation -- the
95006      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
95007      * minimize behavior implemented for this option to be useful.
95008      */
95009     minimizable: false,
95010
95011     /**
95012      * @cfg {Boolean} maximizable
95013      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
95014      * and disallow maximizing the window. Note that when a window is maximized, the tool button
95015      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
95016      * the window to its previous size.
95017      */
95018     maximizable: false,
95019
95020     // inherit docs
95021     minHeight: 100,
95022
95023     // inherit docs
95024     minWidth: 200,
95025
95026     /**
95027      * @cfg {Boolean} expandOnShow
95028      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
95029      * {@link #collapsed}) when displayed.
95030      */
95031     expandOnShow: true,
95032
95033     // inherited docs, same default
95034     collapsible: false,
95035
95036     /**
95037      * @cfg {Boolean} closable
95038      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
95039      * disallow closing the window.
95040      *
95041      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
95042      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
95043      * Window and its content meaning that it may not be reused.
95044      *
95045      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
95046      */
95047     closable: true,
95048
95049     /**
95050      * @cfg {Boolean} hidden
95051      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
95052      */
95053     hidden: true,
95054
95055     // Inherit docs from Component. Windows render to the body on first show.
95056     autoRender: true,
95057
95058     // Inherit docs from Component. Windows hide using visibility.
95059     hideMode: 'visibility',
95060
95061     /** @cfg {Boolean} floating @hide Windows are always floating*/
95062     floating: true,
95063
95064     ariaRole: 'alertdialog',
95065
95066     itemCls: 'x-window-item',
95067
95068     overlapHeader: true,
95069
95070     ignoreHeaderBorderManagement: true,
95071
95072     // private
95073     initComponent: function() {
95074         var me = this;
95075         me.callParent();
95076         me.addEvents(
95077             /**
95078              * @event activate
95079              * Fires after the window has been visually activated via {@link #setActive}.
95080              * @param {Ext.window.Window} this
95081              */
95082
95083             /**
95084              * @event deactivate
95085              * Fires after the window has been visually deactivated via {@link #setActive}.
95086              * @param {Ext.window.Window} this
95087              */
95088
95089             /**
95090              * @event resize
95091              * Fires after the window has been resized.
95092              * @param {Ext.window.Window} this
95093              * @param {Number} width The window's new width
95094              * @param {Number} height The window's new height
95095              */
95096             'resize',
95097
95098             /**
95099              * @event maximize
95100              * Fires after the window has been maximized.
95101              * @param {Ext.window.Window} this
95102              */
95103             'maximize',
95104
95105             /**
95106              * @event minimize
95107              * Fires after the window has been minimized.
95108              * @param {Ext.window.Window} this
95109              */
95110             'minimize',
95111
95112             /**
95113              * @event restore
95114              * Fires after the window has been restored to its original size after being maximized.
95115              * @param {Ext.window.Window} this
95116              */
95117             'restore'
95118         );
95119
95120         if (me.plain) {
95121             me.addClsWithUI('plain');
95122         }
95123
95124         if (me.modal) {
95125             me.ariaRole = 'dialog';
95126         }
95127     },
95128
95129     // State Management
95130     // private
95131
95132     initStateEvents: function(){
95133         var events = this.stateEvents;
95134         // push on stateEvents if they don't exist
95135         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
95136             if (Ext.Array.indexOf(events, event)) {
95137                 events.push(event);
95138             }
95139         });
95140         this.callParent();
95141     },
95142
95143     getState: function() {
95144         var me = this,
95145             state = me.callParent() || {},
95146             maximized = !!me.maximized;
95147
95148         state.maximized = maximized;
95149         Ext.apply(state, {
95150             size: maximized ? me.restoreSize : me.getSize(),
95151             pos: maximized ? me.restorePos : me.getPosition()
95152         });
95153         return state;
95154     },
95155
95156     applyState: function(state){
95157         var me = this;
95158
95159         if (state) {
95160             me.maximized = state.maximized;
95161             if (me.maximized) {
95162                 me.hasSavedRestore = true;
95163                 me.restoreSize = state.size;
95164                 me.restorePos = state.pos;
95165             } else {
95166                 Ext.apply(me, {
95167                     width: state.size.width,
95168                     height: state.size.height,
95169                     x: state.pos[0],
95170                     y: state.pos[1]
95171                 });
95172             }
95173         }
95174     },
95175
95176     // private
95177     onMouseDown: function (e) {
95178         var preventFocus;
95179             
95180         if (this.floating) {
95181             if (Ext.fly(e.getTarget()).focusable()) {
95182                 preventFocus = true;
95183             }
95184             this.toFront(preventFocus);
95185         }
95186     },
95187
95188     // private
95189     onRender: function(ct, position) {
95190         var me = this;
95191         me.callParent(arguments);
95192         me.focusEl = me.el;
95193
95194         // Double clicking a header will toggleMaximize
95195         if (me.maximizable) {
95196             me.header.on({
95197                 dblclick: {
95198                     fn: me.toggleMaximize,
95199                     element: 'el',
95200                     scope: me
95201                 }
95202             });
95203         }
95204     },
95205
95206     // private
95207     afterRender: function() {
95208         var me = this,
95209             hidden = me.hidden,
95210             keyMap;
95211
95212         me.hidden = false;
95213         // Component's afterRender sizes and positions the Component
95214         me.callParent();
95215         me.hidden = hidden;
95216
95217         // Create the proxy after the size has been applied in Component.afterRender
95218         me.proxy = me.getProxy();
95219
95220         // clickToRaise
95221         me.mon(me.el, 'mousedown', me.onMouseDown, me);
95222         
95223         // allow the element to be focusable
95224         me.el.set({
95225             tabIndex: -1
95226         });
95227
95228         // Initialize
95229         if (me.maximized) {
95230             me.maximized = false;
95231             me.maximize();
95232         }
95233
95234         if (me.closable) {
95235             keyMap = me.getKeyMap();
95236             keyMap.on(27, me.onEsc, me);
95237
95238             //if (hidden) { ? would be consistent w/before/afterShow...
95239                 keyMap.disable();
95240             //}
95241         }
95242
95243         if (!hidden) {
95244             me.syncMonitorWindowResize();
95245             me.doConstrain();
95246         }
95247     },
95248
95249     /**
95250      * @private
95251      * @override
95252      * Override Component.initDraggable.
95253      * Window uses the header element as the delegate.
95254      */
95255     initDraggable: function() {
95256         var me = this,
95257             ddConfig;
95258
95259         if (!me.header) {
95260             me.updateHeader(true);
95261         }
95262
95263         /*
95264          * Check the header here again. If for whatever reason it wasn't created in
95265          * updateHeader (preventHeader) then we'll just ignore the rest since the
95266          * header acts as the drag handle.
95267          */
95268         if (me.header) {
95269             ddConfig = Ext.applyIf({
95270                 el: me.el,
95271                 delegate: '#' + me.header.id
95272             }, me.draggable);
95273
95274             // Add extra configs if Window is specified to be constrained
95275             if (me.constrain || me.constrainHeader) {
95276                 ddConfig.constrain = me.constrain;
95277                 ddConfig.constrainDelegate = me.constrainHeader;
95278                 ddConfig.constrainTo = me.constrainTo || me.container;
95279             }
95280
95281             /**
95282              * @property {Ext.util.ComponentDragger} dd
95283              * If this Window is configured {@link #draggable}, this property will contain an instance of
95284              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
95285              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
95286              *
95287              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
95288              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
95289              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
95290              */
95291             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
95292             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
95293         }
95294     },
95295
95296     // private
95297     onEsc: function(k, e) {
95298         e.stopEvent();
95299         this[this.closeAction]();
95300     },
95301
95302     // private
95303     beforeDestroy: function() {
95304         var me = this;
95305         if (me.rendered) {
95306             delete this.animateTarget;
95307             me.hide();
95308             Ext.destroy(
95309                 me.keyMap
95310             );
95311         }
95312         me.callParent();
95313     },
95314
95315     /**
95316      * @private
95317      * @override
95318      * Contribute class-specific tools to the header.
95319      * Called by Panel's initTools.
95320      */
95321     addTools: function() {
95322         var me = this;
95323
95324         // Call Panel's initTools
95325         me.callParent();
95326
95327         if (me.minimizable) {
95328             me.addTool({
95329                 type: 'minimize',
95330                 handler: Ext.Function.bind(me.minimize, me, [])
95331             });
95332         }
95333         if (me.maximizable) {
95334             me.addTool({
95335                 type: 'maximize',
95336                 handler: Ext.Function.bind(me.maximize, me, [])
95337             });
95338             me.addTool({
95339                 type: 'restore',
95340                 handler: Ext.Function.bind(me.restore, me, []),
95341                 hidden: true
95342             });
95343         }
95344     },
95345
95346     /**
95347      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
95348      * Container itself will receive focus.
95349      */
95350     getFocusEl: function() {
95351         var me = this,
95352             f = me.focusEl,
95353             defaultComp = me.defaultButton || me.defaultFocus,
95354             t = typeof db,
95355             el,
95356             ct;
95357
95358         if (Ext.isDefined(defaultComp)) {
95359             if (Ext.isNumber(defaultComp)) {
95360                 f = me.query('button')[defaultComp];
95361             } else if (Ext.isString(defaultComp)) {
95362                 f = me.down('#' + defaultComp);
95363             } else {
95364                 f = defaultComp;
95365             }
95366         }
95367         return f || me.focusEl;
95368     },
95369
95370     // private
95371     beforeShow: function() {
95372         this.callParent();
95373
95374         if (this.expandOnShow) {
95375             this.expand(false);
95376         }
95377     },
95378
95379     // private
95380     afterShow: function(animateTarget) {
95381         var me = this,
95382             animating = animateTarget || me.animateTarget;
95383
95384
95385         // No constraining code needs to go here.
95386         // Component.onShow constrains the Component. *If the constrain config is true*
95387
95388         // Perform superclass's afterShow tasks
95389         // Which might include animating a proxy from an animateTarget
95390         me.callParent(arguments);
95391
95392         if (me.maximized) {
95393             me.fitContainer();
95394         }
95395
95396         me.syncMonitorWindowResize();
95397         if (!animating) {
95398             me.doConstrain();
95399         }
95400
95401         if (me.keyMap) {
95402             me.keyMap.enable();
95403         }
95404     },
95405
95406     // private
95407     doClose: function() {
95408         var me = this;
95409
95410         // Being called as callback after going through the hide call below
95411         if (me.hidden) {
95412             me.fireEvent('close', me);
95413             if (me.closeAction == 'destroy') {
95414                 this.destroy();
95415             }
95416         } else {
95417             // close after hiding
95418             me.hide(me.animateTarget, me.doClose, me);
95419         }
95420     },
95421
95422     // private
95423     afterHide: function() {
95424         var me = this;
95425
95426         // No longer subscribe to resizing now that we're hidden
95427         me.syncMonitorWindowResize();
95428
95429         // Turn off keyboard handling once window is hidden
95430         if (me.keyMap) {
95431             me.keyMap.disable();
95432         }
95433
95434         // Perform superclass's afterHide tasks.
95435         me.callParent(arguments);
95436     },
95437
95438     // private
95439     onWindowResize: function() {
95440         if (this.maximized) {
95441             this.fitContainer();
95442         }
95443         this.doConstrain();
95444     },
95445
95446     /**
95447      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
95448      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
95449      * the minimize event can be handled or this method can be overridden.
95450      * @return {Ext.window.Window} this
95451      */
95452     minimize: function() {
95453         this.fireEvent('minimize', this);
95454         return this;
95455     },
95456
95457     afterCollapse: function() {
95458         var me = this;
95459
95460         if (me.maximizable) {
95461             me.tools.maximize.hide();
95462             me.tools.restore.hide();
95463         }
95464         if (me.resizer) {
95465             me.resizer.disable();
95466         }
95467         me.callParent(arguments);
95468     },
95469
95470     afterExpand: function() {
95471         var me = this;
95472
95473         if (me.maximized) {
95474             me.tools.restore.show();
95475         } else if (me.maximizable) {
95476             me.tools.maximize.show();
95477         }
95478         if (me.resizer) {
95479             me.resizer.enable();
95480         }
95481         me.callParent(arguments);
95482     },
95483
95484     /**
95485      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
95486      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
95487      * @return {Ext.window.Window} this
95488      */
95489     maximize: function() {
95490         var me = this;
95491
95492         if (!me.maximized) {
95493             me.expand(false);
95494             if (!me.hasSavedRestore) {
95495                 me.restoreSize = me.getSize();
95496                 me.restorePos = me.getPosition(true);
95497             }
95498             if (me.maximizable) {
95499                 me.tools.maximize.hide();
95500                 me.tools.restore.show();
95501             }
95502             me.maximized = true;
95503             me.el.disableShadow();
95504
95505             if (me.dd) {
95506                 me.dd.disable();
95507             }
95508             if (me.collapseTool) {
95509                 me.collapseTool.hide();
95510             }
95511             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
95512             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95513
95514             me.syncMonitorWindowResize();
95515             me.setPosition(0, 0);
95516             me.fitContainer();
95517             me.fireEvent('maximize', me);
95518         }
95519         return me;
95520     },
95521
95522     /**
95523      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
95524      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
95525      * @return {Ext.window.Window} this
95526      */
95527     restore: function() {
95528         var me = this,
95529             tools = me.tools;
95530
95531         if (me.maximized) {
95532             delete me.hasSavedRestore;
95533             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
95534
95535             // Toggle tool visibility
95536             if (tools.restore) {
95537                 tools.restore.hide();
95538             }
95539             if (tools.maximize) {
95540                 tools.maximize.show();
95541             }
95542             if (me.collapseTool) {
95543                 me.collapseTool.show();
95544             }
95545
95546             // Restore the position/sizing
95547             me.setPosition(me.restorePos);
95548             me.setSize(me.restoreSize);
95549
95550             // Unset old position/sizing
95551             delete me.restorePos;
95552             delete me.restoreSize;
95553
95554             me.maximized = false;
95555
95556             me.el.enableShadow(true);
95557
95558             // Allow users to drag and drop again
95559             if (me.dd) {
95560                 me.dd.enable();
95561             }
95562
95563             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95564
95565             me.syncMonitorWindowResize();
95566             me.doConstrain();
95567             me.fireEvent('restore', me);
95568         }
95569         return me;
95570     },
95571
95572     /**
95573      * Synchronizes the presence of our listener for window resize events. This method
95574      * should be called whenever this status might change.
95575      * @private
95576      */
95577     syncMonitorWindowResize: function () {
95578         var me = this,
95579             currentlyMonitoring = me._monitoringResize,
95580             // all the states where we should be listening to window resize:
95581             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
95582             // all the states where we veto this:
95583             veto = me.hidden || me.destroying || me.isDestroyed;
95584
95585         if (yes && !veto) {
95586             // we should be listening...
95587             if (!currentlyMonitoring) {
95588                 // but we aren't, so set it up
95589                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
95590                 me._monitoringResize = true;
95591             }
95592         } else if (currentlyMonitoring) {
95593             // we should not be listening, but we are, so tear it down
95594             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
95595             me._monitoringResize = false;
95596         }
95597     },
95598
95599     /**
95600      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
95601      * state of the window.
95602      * @return {Ext.window.Window} this
95603      */
95604     toggleMaximize: function() {
95605         return this[this.maximized ? 'restore': 'maximize']();
95606     }
95607
95608     /**
95609      * @cfg {Boolean} autoWidth @hide
95610      * Absolute positioned element and therefore cannot support autoWidth.
95611      * A width is a required configuration.
95612      **/
95613 });
95614
95615 /**
95616  * @docauthor Jason Johnston <jason@sencha.com>
95617  *
95618  * Base class for form fields that provides default event handling, rendering, and other common functionality
95619  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
95620  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
95621  *
95622  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
95623  * rather than creating instances of this class directly. However if you are implementing a custom form field,
95624  * using this as the parent class is recommended.
95625  *
95626  * # Values and Conversions
95627  *
95628  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
95629  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
95630  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
95631  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
95632  * input, this value data type can not always be directly used in the rendered field.
95633  *
95634  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
95635  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
95636  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
95637  *
95638  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
95639  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
95640  * should override these methods to handle the conversion.
95641  *
95642  * # Rendering
95643  *
95644  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
95645  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
95646  * field renderings.
95647  *
95648  * # Example usage:
95649  *
95650  *     @example
95651  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
95652  *     // searchUrl when the Enter key is pressed.222
95653  *     Ext.define('Ext.form.SearchField', {
95654  *         extend: 'Ext.form.field.Base',
95655  *         alias: 'widget.searchfield',
95656  *     
95657  *         inputType: 'search',
95658  *     
95659  *         // Config defining the search URL
95660  *         searchUrl: 'http://www.google.com/search?q={0}',
95661  *     
95662  *         // Add specialkey listener
95663  *         initComponent: function() {
95664  *             this.callParent();
95665  *             this.on('specialkey', this.checkEnterKey, this);
95666  *         },
95667  *     
95668  *         // Handle enter key presses, execute the search if the field has a value
95669  *         checkEnterKey: function(field, e) {
95670  *             var value = this.getValue();
95671  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
95672  *                 location.href = Ext.String.format(this.searchUrl, value);
95673  *             }
95674  *         }
95675  *     });
95676  *     
95677  *     Ext.create('Ext.form.Panel', {
95678  *         title: 'BaseField Example',
95679  *         bodyPadding: 5,
95680  *         width: 250,
95681  *     
95682  *         // Fields will be arranged vertically, stretched to full width
95683  *         layout: 'anchor',
95684  *         defaults: {
95685  *             anchor: '100%'
95686  *         },
95687  *         items: [{
95688  *             xtype: 'searchfield',
95689  *             fieldLabel: 'Search',
95690  *             name: 'query'
95691  *         }],
95692  *         renderTo: Ext.getBody()
95693  *     });
95694  */
95695 Ext.define('Ext.form.field.Base', {
95696     extend: 'Ext.Component',
95697     mixins: {
95698         labelable: 'Ext.form.Labelable',
95699         field: 'Ext.form.field.Field'
95700     },
95701     alias: 'widget.field',
95702     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
95703     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
95704
95705     /**
95706      * @cfg {Ext.XTemplate} fieldSubTpl
95707      * The content of the field body is defined by this config option.
95708      */
95709     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
95710         '<input id="{id}" type="{type}" ',
95711         '<tpl if="name">name="{name}" </tpl>',
95712         '<tpl if="size">size="{size}" </tpl>',
95713         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
95714         'class="{fieldCls} {typeCls}" autocomplete="off" />',
95715         {
95716             compiled: true,
95717             disableFormats: true
95718         }
95719     ],
95720
95721     /**
95722      * @cfg {String} name
95723      * The name of the field. This is used as the parameter name when including the field value
95724      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
95725      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
95726      */
95727
95728     /**
95729      * @cfg {String} inputType
95730      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
95731      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
95732      * fall back to 'text'.
95733      *
95734      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
95735      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
95736      * a plain unstyled file input you can use a BaseField with inputType:'file'.
95737      */
95738     inputType: 'text',
95739
95740     /**
95741      * @cfg {Number} tabIndex
95742      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
95743      * applyTo
95744      */
95745
95746     /**
95747      * @cfg {String} invalidText
95748      * The error text to use when marking a field invalid and no message is provided
95749      */
95750     invalidText : 'The value in this field is invalid',
95751
95752     /**
95753      * @cfg {String} [fieldCls='x-form-field']
95754      * The default CSS class for the field input
95755      */
95756     fieldCls : Ext.baseCSSPrefix + 'form-field',
95757
95758     /**
95759      * @cfg {String} fieldStyle
95760      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
95761      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
95762      * the style after initialization.
95763      */
95764
95765     /**
95766      * @cfg {String} [focusCls='x-form-focus']
95767      * The CSS class to use when the field receives focus
95768      */
95769     focusCls : Ext.baseCSSPrefix + 'form-focus',
95770
95771     /**
95772      * @cfg {String} dirtyCls
95773      * The CSS class to use when the field value {@link #isDirty is dirty}.
95774      */
95775     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
95776
95777     /**
95778      * @cfg {String[]} checkChangeEvents
95779      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
95780      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
95781      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
95782      *
95783      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
95784      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
95785      * browsers; the only known exceptions at the time of writing are:
95786      *
95787      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
95788      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
95789      *     and textareas
95790      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
95791      *
95792      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
95793      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
95794      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
95795      * such a task automatically.
95796      */
95797     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
95798                         ['change', 'propertychange'] :
95799                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
95800
95801     /**
95802      * @cfg {Number} checkChangeBuffer
95803      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
95804      * Defaults to 50 milliseconds.
95805      */
95806     checkChangeBuffer: 50,
95807
95808     componentLayout: 'field',
95809
95810     /**
95811      * @cfg {Boolean} readOnly
95812      * true to mark the field as readOnly in HTML.
95813      *
95814      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
95815      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
95816      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
95817      */
95818     readOnly : false,
95819
95820     /**
95821      * @cfg {String} readOnlyCls
95822      * The CSS class applied to the component's main element when it is {@link #readOnly}.
95823      */
95824     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
95825
95826     /**
95827      * @cfg {String} inputId
95828      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
95829      * configure this manually, you must make sure it is unique in the document.
95830      */
95831
95832     /**
95833      * @cfg {Boolean} validateOnBlur
95834      * Whether the field should validate when it loses focus. This will cause fields to be validated
95835      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
95836      * along the way. See also {@link #validateOnChange}.
95837      */
95838     validateOnBlur: true,
95839
95840     // private
95841     hasFocus : false,
95842
95843     baseCls: Ext.baseCSSPrefix + 'field',
95844
95845     maskOnDisable: false,
95846
95847     // private
95848     initComponent : function() {
95849         var me = this;
95850
95851         me.callParent();
95852
95853         me.subTplData = me.subTplData || {};
95854
95855         me.addEvents(
95856             /**
95857              * @event focus
95858              * Fires when this field receives input focus.
95859              * @param {Ext.form.field.Base} this
95860              */
95861             'focus',
95862             /**
95863              * @event blur
95864              * Fires when this field loses input focus.
95865              * @param {Ext.form.field.Base} this
95866              */
95867             'blur',
95868             /**
95869              * @event specialkey
95870              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
95871              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
95872              * pressed. For example:
95873              *
95874              *     var form = new Ext.form.Panel({
95875              *         ...
95876              *         items: [{
95877              *                 fieldLabel: 'Field 1',
95878              *                 name: 'field1',
95879              *                 allowBlank: false
95880              *             },{
95881              *                 fieldLabel: 'Field 2',
95882              *                 name: 'field2',
95883              *                 listeners: {
95884              *                     specialkey: function(field, e){
95885              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
95886              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
95887              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
95888              *                             var form = field.up('form').getForm();
95889              *                             form.submit();
95890              *                         }
95891              *                     }
95892              *                 }
95893              *             }
95894              *         ],
95895              *         ...
95896              *     });
95897              *
95898              * @param {Ext.form.field.Base} this
95899              * @param {Ext.EventObject} e The event object
95900              */
95901             'specialkey'
95902         );
95903
95904         // Init mixins
95905         me.initLabelable();
95906         me.initField();
95907
95908         // Default name to inputId
95909         if (!me.name) {
95910             me.name = me.getInputId();
95911         }
95912     },
95913
95914     /**
95915      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
95916      * automatically generated.
95917      */
95918     getInputId: function() {
95919         return this.inputId || (this.inputId = Ext.id());
95920     },
95921
95922     /**
95923      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
95924      * @return {Object} The template data
95925      * @template
95926      */
95927     getSubTplData: function() {
95928         var me = this,
95929             type = me.inputType,
95930             inputId = me.getInputId();
95931
95932         return Ext.applyIf(me.subTplData, {
95933             id: inputId,
95934             cmpId: me.id,
95935             name: me.name || inputId,
95936             type: type,
95937             size: me.size || 20,
95938             cls: me.cls,
95939             fieldCls: me.fieldCls,
95940             tabIdx: me.tabIndex,
95941             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
95942         });
95943     },
95944
95945     afterRender: function() {
95946         this.callParent();
95947         
95948         if (this.inputEl) {
95949             this.inputEl.selectable();
95950         }
95951     },
95952
95953     /**
95954      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
95955      */
95956     getSubTplMarkup: function() {
95957         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
95958     },
95959
95960     initRenderTpl: function() {
95961         var me = this;
95962         if (!me.hasOwnProperty('renderTpl')) {
95963             me.renderTpl = me.getTpl('labelableRenderTpl');
95964         }
95965         return me.callParent();
95966     },
95967
95968     initRenderData: function() {
95969         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
95970     },
95971
95972     /**
95973      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
95974      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
95975      * Ext.Element#applyStyles}.
95976      */
95977     setFieldStyle: function(style) {
95978         var me = this,
95979             inputEl = me.inputEl;
95980         if (inputEl) {
95981             inputEl.applyStyles(style);
95982         }
95983         me.fieldStyle = style;
95984     },
95985
95986     // private
95987     onRender : function() {
95988         var me = this,
95989             fieldStyle = me.fieldStyle;
95990
95991         me.onLabelableRender();
95992
95993         /**
95994          * @property {Ext.Element} inputEl
95995          * The input Element for this Field. Only available after the field has been rendered.
95996          */
95997         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
95998
95999         me.callParent(arguments);
96000
96001         // Make the stored rawValue get set as the input element's value
96002         me.setRawValue(me.rawValue);
96003
96004         if (me.readOnly) {
96005             me.setReadOnly(true);
96006         }
96007         if (me.disabled) {
96008             me.disable();
96009         }
96010         if (fieldStyle) {
96011             me.setFieldStyle(fieldStyle);
96012         }
96013
96014         me.renderActiveError();
96015     },
96016
96017     initAria: function() {
96018         var me = this;
96019         me.callParent();
96020
96021         // Associate the field to the error message element
96022         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
96023     },
96024
96025     getFocusEl: function() {
96026         return this.inputEl;
96027     },
96028
96029     isFileUpload: function() {
96030         return this.inputType === 'file';
96031     },
96032
96033     extractFileInput: function() {
96034         var me = this,
96035             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
96036             clone;
96037         if (fileInput) {
96038             clone = fileInput.cloneNode(true);
96039             fileInput.parentNode.replaceChild(clone, fileInput);
96040             me.inputEl = Ext.get(clone);
96041         }
96042         return fileInput;
96043     },
96044
96045     // private override to use getSubmitValue() as a convenience
96046     getSubmitData: function() {
96047         var me = this,
96048             data = null,
96049             val;
96050         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
96051             val = me.getSubmitValue();
96052             if (val !== null) {
96053                 data = {};
96054                 data[me.getName()] = val;
96055             }
96056         }
96057         return data;
96058     },
96059
96060     /**
96061      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
96062      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
96063      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
96064      *
96065      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
96066      * successfully {@link #validate validated}.
96067      *
96068      * @return {String} The value to be submitted, or null.
96069      */
96070     getSubmitValue: function() {
96071         return this.processRawValue(this.getRawValue());
96072     },
96073
96074     /**
96075      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
96076      * normalized and converted value see {@link #getValue}.
96077      * @return {String} value The raw String value of the field
96078      */
96079     getRawValue: function() {
96080         var me = this,
96081             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
96082         me.rawValue = v;
96083         return v;
96084     },
96085
96086     /**
96087      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
96088      * validation. To set the value with these additional inspections see {@link #setValue}.
96089      * @param {Object} value The value to set
96090      * @return {Object} value The field value that is set
96091      */
96092     setRawValue: function(value) {
96093         var me = this;
96094         value = Ext.value(value, '');
96095         me.rawValue = value;
96096
96097         // Some Field subclasses may not render an inputEl
96098         if (me.inputEl) {
96099             me.inputEl.dom.value = value;
96100         }
96101         return value;
96102     },
96103
96104     /**
96105      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
96106      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
96107      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
96108      * to a String for display in the field.
96109      *
96110      * See {@link #rawToValue} for the opposite conversion.
96111      *
96112      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
96113      * to an empty string.
96114      *
96115      * @param {Object} value The mixed-type value to convert to the raw representation.
96116      * @return {Object} The converted raw value.
96117      */
96118     valueToRaw: function(value) {
96119         return '' + Ext.value(value, '');
96120     },
96121
96122     /**
96123      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
96124      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
96125      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
96126      *
96127      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
96128      * to the desired object type.
96129      *
96130      * See {@link #valueToRaw} for the opposite conversion.
96131      *
96132      * The base implementation does no conversion, returning the raw value untouched.
96133      *
96134      * @param {Object} rawValue
96135      * @return {Object} The converted value.
96136      */
96137     rawToValue: function(rawValue) {
96138         return rawValue;
96139     },
96140
96141     /**
96142      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
96143      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
96144      * nothing; individual subclasses may override this as needed.
96145      *
96146      * @param {Object} value The unprocessed string value
96147      * @return {Object} The processed string value
96148      */
96149     processRawValue: function(value) {
96150         return value;
96151     },
96152
96153     /**
96154      * Returns the current data value of the field. The type of value returned is particular to the type of the
96155      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
96156      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
96157      * @return {Object} value The field value
96158      */
96159     getValue: function() {
96160         var me = this,
96161             val = me.rawToValue(me.processRawValue(me.getRawValue()));
96162         me.value = val;
96163         return val;
96164     },
96165
96166     /**
96167      * Sets a data value into the field and runs the change detection and validation. To set the value directly
96168      * without these inspections see {@link #setRawValue}.
96169      * @param {Object} value The value to set
96170      * @return {Ext.form.field.Field} this
96171      */
96172     setValue: function(value) {
96173         var me = this;
96174         me.setRawValue(me.valueToRaw(value));
96175         return me.mixins.field.setValue.call(me, value);
96176     },
96177
96178
96179     //private
96180     onDisable: function() {
96181         var me = this,
96182             inputEl = me.inputEl;
96183         me.callParent();
96184         if (inputEl) {
96185             inputEl.dom.disabled = true;
96186         }
96187     },
96188
96189     //private
96190     onEnable: function() {
96191         var me = this,
96192             inputEl = me.inputEl;
96193         me.callParent();
96194         if (inputEl) {
96195             inputEl.dom.disabled = false;
96196         }
96197     },
96198
96199     /**
96200      * Sets the read only state of this field.
96201      * @param {Boolean} readOnly Whether the field should be read only.
96202      */
96203     setReadOnly: function(readOnly) {
96204         var me = this,
96205             inputEl = me.inputEl;
96206         if (inputEl) {
96207             inputEl.dom.readOnly = readOnly;
96208             inputEl.dom.setAttribute('aria-readonly', readOnly);
96209         }
96210         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
96211         me.readOnly = readOnly;
96212     },
96213
96214     // private
96215     fireKey: function(e){
96216         if(e.isSpecialKey()){
96217             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
96218         }
96219     },
96220
96221     // private
96222     initEvents : function(){
96223         var me = this,
96224             inputEl = me.inputEl,
96225             onChangeTask,
96226             onChangeEvent;
96227         if (inputEl) {
96228             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
96229             me.mon(inputEl, 'focus', me.onFocus, me);
96230
96231             // standardise buffer across all browsers + OS-es for consistent event order.
96232             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
96233             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
96234
96235             // listen for immediate value changes
96236             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
96237             me.onChangeEvent = onChangeEvent = function() {
96238                 onChangeTask.delay(me.checkChangeBuffer);
96239             };
96240             Ext.each(me.checkChangeEvents, function(eventName) {
96241                 if (eventName === 'propertychange') {
96242                     me.usesPropertychange = true;
96243                 }
96244                 me.mon(inputEl, eventName, onChangeEvent);
96245             }, me);
96246         }
96247         me.callParent();
96248     },
96249
96250     doComponentLayout: function() {
96251         var me = this,
96252             inputEl = me.inputEl,
96253             usesPropertychange = me.usesPropertychange,
96254             ename = 'propertychange',
96255             onChangeEvent = me.onChangeEvent;
96256
96257         // In IE if propertychange is one of the checkChangeEvents, we need to remove
96258         // the listener prior to layout and re-add it after, to prevent it from firing
96259         // needlessly for attribute and style changes applied to the inputEl.
96260         if (usesPropertychange) {
96261             me.mun(inputEl, ename, onChangeEvent);
96262         }
96263         me.callParent(arguments);
96264         if (usesPropertychange) {
96265             me.mon(inputEl, ename, onChangeEvent);
96266         }
96267     },
96268
96269     // private
96270     preFocus: Ext.emptyFn,
96271
96272     // private
96273     onFocus: function() {
96274         var me = this,
96275             focusCls = me.focusCls,
96276             inputEl = me.inputEl;
96277         me.preFocus();
96278         if (focusCls && inputEl) {
96279             inputEl.addCls(focusCls);
96280         }
96281         if (!me.hasFocus) {
96282             me.hasFocus = true;
96283             me.componentLayout.onFocus();
96284             me.fireEvent('focus', me);
96285         }
96286     },
96287
96288     // private
96289     beforeBlur : Ext.emptyFn,
96290
96291     // private
96292     onBlur : function(){
96293         var me = this,
96294             focusCls = me.focusCls,
96295             inputEl = me.inputEl;
96296
96297         if (me.destroying) {
96298             return;
96299         }
96300
96301         me.beforeBlur();
96302         if (focusCls && inputEl) {
96303             inputEl.removeCls(focusCls);
96304         }
96305         if (me.validateOnBlur) {
96306             me.validate();
96307         }
96308         me.hasFocus = false;
96309         me.fireEvent('blur', me);
96310         me.postBlur();
96311     },
96312
96313     // private
96314     postBlur : Ext.emptyFn,
96315
96316
96317     /**
96318      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
96319      * @param {Boolean} isDirty
96320      */
96321     onDirtyChange: function(isDirty) {
96322         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
96323     },
96324
96325
96326     /**
96327      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
96328      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
96329      * always treated as valid.
96330      *
96331      * @return {Boolean} True if the value is valid, else false
96332      */
96333     isValid : function() {
96334         var me = this;
96335         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
96336     },
96337
96338
96339     /**
96340      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
96341      * {@link #markInvalid} and false is returned, otherwise true is returned.
96342      *
96343      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
96344      * onwards {@link #getErrors} should be overridden instead.
96345      *
96346      * @param {Object} value The value to validate
96347      * @return {Boolean} True if all validations passed, false if one or more failed
96348      */
96349     validateValue: function(value) {
96350         var me = this,
96351             errors = me.getErrors(value),
96352             isValid = Ext.isEmpty(errors);
96353         if (!me.preventMark) {
96354             if (isValid) {
96355                 me.clearInvalid();
96356             } else {
96357                 me.markInvalid(errors);
96358             }
96359         }
96360
96361         return isValid;
96362     },
96363
96364     /**
96365      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
96366      * display the messages and applying {@link #invalidCls} to the field's UI element.
96367      *
96368      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
96369      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
96370      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96371      *
96372      * @param {String/String[]} errors The validation message(s) to display.
96373      */
96374     markInvalid : function(errors) {
96375         // Save the message and fire the 'invalid' event
96376         var me = this,
96377             oldMsg = me.getActiveError();
96378         me.setActiveErrors(Ext.Array.from(errors));
96379         if (oldMsg !== me.getActiveError()) {
96380             me.doComponentLayout();
96381         }
96382     },
96383
96384     /**
96385      * Clear any invalid styles/messages for this field.
96386      *
96387      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
96388      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
96389      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96390      */
96391     clearInvalid : function() {
96392         // Clear the message and fire the 'valid' event
96393         var me = this,
96394             hadError = me.hasActiveError();
96395         me.unsetActiveError();
96396         if (hadError) {
96397             me.doComponentLayout();
96398         }
96399     },
96400
96401     /**
96402      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
96403      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
96404      */
96405     renderActiveError: function() {
96406         var me = this,
96407             hasError = me.hasActiveError();
96408         if (me.inputEl) {
96409             // Add/remove invalid class
96410             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
96411         }
96412         me.mixins.labelable.renderActiveError.call(me);
96413     },
96414
96415
96416     getActionEl: function() {
96417         return this.inputEl || this.el;
96418     }
96419
96420 });
96421
96422 /**
96423  * @docauthor Jason Johnston <jason@sencha.com>
96424  *
96425  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
96426  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
96427  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
96428  *
96429  * # Validation
96430  *
96431  * The Text field has a useful set of validations built in:
96432  *
96433  * - {@link #allowBlank} for making the field required
96434  * - {@link #minLength} for requiring a minimum value length
96435  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
96436  *   as the `maxlength` attribute on the input element)
96437  * - {@link #regex} to specify a custom regular expression for validation
96438  *
96439  * In addition, custom validations may be added:
96440  *
96441  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
96442  *   custom validation logic
96443  * - {@link #validator} allows a custom arbitrary function to be called during validation
96444  *
96445  * The details around how and when each of these validation options get used are described in the
96446  * documentation for {@link #getErrors}.
96447  *
96448  * By default, the field value is checked for validity immediately while the user is typing in the
96449  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
96450  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
96451  * {@link Ext.form.Panel} class documentation.
96452  *
96453  * # Masking and Character Stripping
96454  *
96455  * Text fields can be configured with custom regular expressions to be applied to entered values before
96456  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
96457  *
96458  * # Example usage
96459  *
96460  *     @example
96461  *     Ext.create('Ext.form.Panel', {
96462  *         title: 'Contact Info',
96463  *         width: 300,
96464  *         bodyPadding: 10,
96465  *         renderTo: Ext.getBody(),
96466  *         items: [{
96467  *             xtype: 'textfield',
96468  *             name: 'name',
96469  *             fieldLabel: 'Name',
96470  *             allowBlank: false  // requires a non-empty value
96471  *         }, {
96472  *             xtype: 'textfield',
96473  *             name: 'email',
96474  *             fieldLabel: 'Email Address',
96475  *             vtype: 'email'  // requires value to be a valid email address format
96476  *         }]
96477  *     });
96478  */
96479 Ext.define('Ext.form.field.Text', {
96480     extend:'Ext.form.field.Base',
96481     alias: 'widget.textfield',
96482     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
96483     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
96484
96485     /**
96486      * @cfg {String} vtypeText
96487      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
96488      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
96489      */
96490
96491     /**
96492      * @cfg {RegExp} stripCharsRe
96493      * A JavaScript RegExp object used to strip unwanted content from the value
96494      * before validation. If <tt>stripCharsRe</tt> is specified,
96495      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
96496      * This does not change the value of the field.
96497      */
96498
96499     /**
96500      * @cfg {Number} size
96501      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
96502      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
96503      */
96504     size: 20,
96505
96506     /**
96507      * @cfg {Boolean} [grow=false]
96508      * true if this field should automatically grow and shrink to its content
96509      */
96510
96511     /**
96512      * @cfg {Number} growMin
96513      * The minimum width to allow when `{@link #grow} = true`
96514      */
96515     growMin : 30,
96516
96517     /**
96518      * @cfg {Number} growMax
96519      * The maximum width to allow when `{@link #grow} = true`
96520      */
96521     growMax : 800,
96522
96523     /**
96524      * @cfg {String} growAppend
96525      * A string that will be appended to the field's current value for the purposes of calculating the target field
96526      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
96527      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
96528      * width is adjusted.
96529      */
96530     growAppend: 'W',
96531
96532     /**
96533      * @cfg {String} vtype
96534      * A validation type name as defined in {@link Ext.form.field.VTypes}
96535      */
96536
96537     /**
96538      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
96539      * typed) that do not match.
96540      * Note: It dose not filter characters already in the input.
96541      */
96542
96543     /**
96544      * @cfg {Boolean} [disableKeyFilter=false]
96545      * Specify true to disable input keystroke filtering
96546      */
96547
96548     /**
96549      * @cfg {Boolean} allowBlank
96550      * Specify false to validate that the value's length is > 0
96551      */
96552     allowBlank : true,
96553
96554     /**
96555      * @cfg {Number} minLength
96556      * Minimum input field length required
96557      */
96558     minLength : 0,
96559
96560     /**
96561      * @cfg {Number} maxLength
96562      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
96563      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
96564      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
96565      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
96566      */
96567     maxLength : Number.MAX_VALUE,
96568
96569     /**
96570      * @cfg {Boolean} enforceMaxLength
96571      * True to set the maxLength property on the underlying input field. Defaults to false
96572      */
96573
96574     /**
96575      * @cfg {String} minLengthText
96576      * Error text to display if the **{@link #minLength minimum length}** validation fails.
96577      */
96578     minLengthText : 'The minimum length for this field is {0}',
96579
96580     /**
96581      * @cfg {String} maxLengthText
96582      * Error text to display if the **{@link #maxLength maximum length}** validation fails
96583      */
96584     maxLengthText : 'The maximum length for this field is {0}',
96585
96586     /**
96587      * @cfg {Boolean} [selectOnFocus=false]
96588      * true to automatically select any existing field text when the field receives input focus
96589      */
96590
96591     /**
96592      * @cfg {String} blankText
96593      * The error text to display if the **{@link #allowBlank}** validation fails
96594      */
96595     blankText : 'This field is required',
96596
96597     /**
96598      * @cfg {Function} validator
96599      * A custom validation function to be called during field validation ({@link #getErrors}).
96600      * If specified, this function will be called first, allowing the developer to override the default validation
96601      * process.
96602      *
96603      * This function will be passed the following parameters:
96604      *
96605      * @cfg {Object} validator.value The current field value
96606      * @cfg {Boolean/String} validator.return
96607      *
96608      * - True if the value is valid
96609      * - An error message if the value is invalid
96610      */
96611
96612     /**
96613      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
96614      * If the test fails, the field will be marked invalid using
96615      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
96616      */
96617
96618     /**
96619      * @cfg {String} regexText
96620      * The error text to display if **{@link #regex}** is used and the test fails during validation
96621      */
96622     regexText : '',
96623
96624     /**
96625      * @cfg {String} emptyText
96626      * The default text to place into an empty field.
96627      *
96628      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
96629      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
96630      * false.
96631      *
96632      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
96633      * avoided.
96634      */
96635
96636     /**
96637      * @cfg {String} [emptyCls='x-form-empty-field']
96638      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
96639      * This class is automatically added and removed as needed depending on the current field value.
96640      */
96641     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
96642
96643     ariaRole: 'textbox',
96644
96645     /**
96646      * @cfg {Boolean} [enableKeyEvents=false]
96647      * true to enable the proxying of key events for the HTML input field
96648      */
96649
96650     componentLayout: 'textfield',
96651
96652     initComponent : function(){
96653         this.callParent();
96654         this.addEvents(
96655             /**
96656              * @event autosize
96657              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
96658              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
96659              * developer to apply additional logic at runtime to resize the field if needed.
96660              * @param {Ext.form.field.Text} this This text field
96661              * @param {Number} width The new field width
96662              */
96663             'autosize',
96664
96665             /**
96666              * @event keydown
96667              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96668              * @param {Ext.form.field.Text} this This text field
96669              * @param {Ext.EventObject} e
96670              */
96671             'keydown',
96672             /**
96673              * @event keyup
96674              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96675              * @param {Ext.form.field.Text} this This text field
96676              * @param {Ext.EventObject} e
96677              */
96678             'keyup',
96679             /**
96680              * @event keypress
96681              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96682              * @param {Ext.form.field.Text} this This text field
96683              * @param {Ext.EventObject} e
96684              */
96685             'keypress'
96686         );
96687     },
96688
96689     // private
96690     initEvents : function(){
96691         var me = this,
96692             el = me.inputEl;
96693
96694         me.callParent();
96695         if(me.selectOnFocus || me.emptyText){
96696             me.mon(el, 'mousedown', me.onMouseDown, me);
96697         }
96698         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
96699             me.mon(el, 'keypress', me.filterKeys, me);
96700         }
96701
96702         if (me.enableKeyEvents) {
96703             me.mon(el, {
96704                 scope: me,
96705                 keyup: me.onKeyUp,
96706                 keydown: me.onKeyDown,
96707                 keypress: me.onKeyPress
96708             });
96709         }
96710     },
96711
96712     /**
96713      * @private
96714      * Override. Treat undefined and null values as equal to an empty string value.
96715      */
96716     isEqual: function(value1, value2) {
96717         return this.isEqualAsString(value1, value2);
96718     },
96719
96720     /**
96721      * @private
96722      * If grow=true, invoke the autoSize method when the field's value is changed.
96723      */
96724     onChange: function() {
96725         this.callParent();
96726         this.autoSize();
96727     },
96728
96729     afterRender: function(){
96730         var me = this;
96731         if (me.enforceMaxLength) {
96732             me.inputEl.dom.maxLength = me.maxLength;
96733         }
96734         me.applyEmptyText();
96735         me.autoSize();
96736         me.callParent();
96737     },
96738
96739     onMouseDown: function(e){
96740         var me = this;
96741         if(!me.hasFocus){
96742             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
96743         }
96744     },
96745
96746     /**
96747      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
96748      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
96749      * to the raw value.
96750      * @param {String} value The unprocessed string value
96751      * @return {String} The processed string value
96752      */
96753     processRawValue: function(value) {
96754         var me = this,
96755             stripRe = me.stripCharsRe,
96756             newValue;
96757
96758         if (stripRe) {
96759             newValue = value.replace(stripRe, '');
96760             if (newValue !== value) {
96761                 me.setRawValue(newValue);
96762                 value = newValue;
96763             }
96764         }
96765         return value;
96766     },
96767
96768     //private
96769     onDisable: function(){
96770         this.callParent();
96771         if (Ext.isIE) {
96772             this.inputEl.dom.unselectable = 'on';
96773         }
96774     },
96775
96776     //private
96777     onEnable: function(){
96778         this.callParent();
96779         if (Ext.isIE) {
96780             this.inputEl.dom.unselectable = '';
96781         }
96782     },
96783
96784     onKeyDown: function(e) {
96785         this.fireEvent('keydown', this, e);
96786     },
96787
96788     onKeyUp: function(e) {
96789         this.fireEvent('keyup', this, e);
96790     },
96791
96792     onKeyPress: function(e) {
96793         this.fireEvent('keypress', this, e);
96794     },
96795
96796     /**
96797      * Resets the current field value to the originally-loaded value and clears any validation messages.
96798      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
96799      */
96800     reset : function(){
96801         this.callParent();
96802         this.applyEmptyText();
96803     },
96804
96805     applyEmptyText : function(){
96806         var me = this,
96807             emptyText = me.emptyText,
96808             isEmpty;
96809
96810         if (me.rendered && emptyText) {
96811             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
96812
96813             if (Ext.supports.Placeholder) {
96814                 me.inputEl.dom.placeholder = emptyText;
96815             } else if (isEmpty) {
96816                 me.setRawValue(emptyText);
96817             }
96818
96819             //all browsers need this because of a styling issue with chrome + placeholders.
96820             //the text isnt vertically aligned when empty (and using the placeholder)
96821             if (isEmpty) {
96822                 me.inputEl.addCls(me.emptyCls);
96823             }
96824
96825             me.autoSize();
96826         }
96827     },
96828
96829     // private
96830     preFocus : function(){
96831         var me = this,
96832             inputEl = me.inputEl,
96833             emptyText = me.emptyText,
96834             isEmpty;
96835
96836         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
96837             me.setRawValue('');
96838             isEmpty = true;
96839             inputEl.removeCls(me.emptyCls);
96840         } else if (Ext.supports.Placeholder) {
96841             me.inputEl.removeCls(me.emptyCls);
96842         }
96843         if (me.selectOnFocus || isEmpty) {
96844             inputEl.dom.select();
96845         }
96846     },
96847
96848     onFocus: function() {
96849         var me = this;
96850         me.callParent(arguments);
96851         if (me.emptyText) {
96852             me.autoSize();
96853         }
96854     },
96855
96856     // private
96857     postBlur : function(){
96858         this.applyEmptyText();
96859     },
96860
96861     // private
96862     filterKeys : function(e){
96863         /*
96864          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
96865          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
96866          * so we can still process these special characters.
96867          */
96868         if (e.ctrlKey && !e.altKey) {
96869             return;
96870         }
96871         var key = e.getKey(),
96872             charCode = String.fromCharCode(e.getCharCode());
96873
96874         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
96875             return;
96876         }
96877
96878         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
96879             return;
96880         }
96881         if(!this.maskRe.test(charCode)){
96882             e.stopEvent();
96883         }
96884     },
96885
96886     /**
96887      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
96888      * the current value of the input element if the field has been rendered, ignoring the value if it is the
96889      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
96890      * @return {String} The raw String value of the field
96891      */
96892     getRawValue: function() {
96893         var me = this,
96894             v = me.callParent();
96895         if (v === me.emptyText) {
96896             v = '';
96897         }
96898         return v;
96899     },
96900
96901     /**
96902      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
96903      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
96904      * @param {Object} value The value to set
96905      * @return {Ext.form.field.Text} this
96906      */
96907     setValue: function(value) {
96908         var me = this,
96909             inputEl = me.inputEl;
96910
96911         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
96912             inputEl.removeCls(me.emptyCls);
96913         }
96914
96915         me.callParent(arguments);
96916
96917         me.applyEmptyText();
96918         return me;
96919     },
96920
96921     /**
96922      * Validates a value according to the field's validation rules and returns an array of errors
96923      * for any failing validations. Validation rules are processed in the following order:
96924      *
96925      * 1. **Field specific validator**
96926      *
96927      *     A validator offers a way to customize and reuse a validation specification.
96928      *     If a field is configured with a `{@link #validator}`
96929      *     function, it will be passed the current field value.  The `{@link #validator}`
96930      *     function is expected to return either:
96931      *
96932      *     - Boolean `true`  if the value is valid (validation continues).
96933      *     - a String to represent the invalid message if invalid (validation halts).
96934      *
96935      * 2. **Basic Validation**
96936      *
96937      *     If the `{@link #validator}` has not halted validation,
96938      *     basic validation proceeds as follows:
96939      *
96940      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
96941      *
96942      *         Depending on the configuration of `{@link #allowBlank}`, a
96943      *         blank field will cause validation to halt at this step and return
96944      *         Boolean true or false accordingly.
96945      *
96946      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
96947      *
96948      *         If the passed value does not satisfy the `{@link #minLength}`
96949      *         specified, validation halts.
96950      *
96951      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
96952      *
96953      *         If the passed value does not satisfy the `{@link #maxLength}`
96954      *         specified, validation halts.
96955      *
96956      * 3. **Preconfigured Validation Types (VTypes)**
96957      *
96958      *     If none of the prior validation steps halts validation, a field
96959      *     configured with a `{@link #vtype}` will utilize the
96960      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
96961      *     If invalid, either the field's `{@link #vtypeText}` or
96962      *     the VTypes vtype Text property will be used for the invalid message.
96963      *     Keystrokes on the field will be filtered according to the VTypes
96964      *     vtype Mask property.
96965      *
96966      * 4. **Field specific regex test**
96967      *
96968      *     If none of the prior validation steps halts validation, a field's
96969      *     configured <code>{@link #regex}</code> test will be processed.
96970      *     The invalid message for this test is configured with `{@link #regexText}`
96971      *
96972      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
96973      * @return {String[]} Array of any validation errors
96974      */
96975     getErrors: function(value) {
96976         var me = this,
96977             errors = me.callParent(arguments),
96978             validator = me.validator,
96979             emptyText = me.emptyText,
96980             allowBlank = me.allowBlank,
96981             vtype = me.vtype,
96982             vtypes = Ext.form.field.VTypes,
96983             regex = me.regex,
96984             format = Ext.String.format,
96985             msg;
96986
96987         value = value || me.processRawValue(me.getRawValue());
96988
96989         if (Ext.isFunction(validator)) {
96990             msg = validator.call(me, value);
96991             if (msg !== true) {
96992                 errors.push(msg);
96993             }
96994         }
96995
96996         if (value.length < 1 || value === emptyText) {
96997             if (!allowBlank) {
96998                 errors.push(me.blankText);
96999             }
97000             //if value is blank, there cannot be any additional errors
97001             return errors;
97002         }
97003
97004         if (value.length < me.minLength) {
97005             errors.push(format(me.minLengthText, me.minLength));
97006         }
97007
97008         if (value.length > me.maxLength) {
97009             errors.push(format(me.maxLengthText, me.maxLength));
97010         }
97011
97012         if (vtype) {
97013             if(!vtypes[vtype](value, me)){
97014                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
97015             }
97016         }
97017
97018         if (regex && !regex.test(value)) {
97019             errors.push(me.regexText || me.invalidText);
97020         }
97021
97022         return errors;
97023     },
97024
97025     /**
97026      * Selects text in this field
97027      * @param {Number} [start=0] The index where the selection should start
97028      * @param {Number} [end] The index where the selection should end (defaults to the text length)
97029      */
97030     selectText : function(start, end){
97031         var me = this,
97032             v = me.getRawValue(),
97033             doFocus = true,
97034             el = me.inputEl.dom,
97035             undef,
97036             range;
97037
97038         if (v.length > 0) {
97039             start = start === undef ? 0 : start;
97040             end = end === undef ? v.length : end;
97041             if (el.setSelectionRange) {
97042                 el.setSelectionRange(start, end);
97043             }
97044             else if(el.createTextRange) {
97045                 range = el.createTextRange();
97046                 range.moveStart('character', start);
97047                 range.moveEnd('character', end - v.length);
97048                 range.select();
97049             }
97050             doFocus = Ext.isGecko || Ext.isOpera;
97051         }
97052         if (doFocus) {
97053             me.focus();
97054         }
97055     },
97056
97057     /**
97058      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
97059      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
97060      */
97061     autoSize: function() {
97062         var me = this,
97063             width;
97064         if (me.grow && me.rendered) {
97065             me.doComponentLayout();
97066             width = me.inputEl.getWidth();
97067             if (width !== me.lastInputWidth) {
97068                 me.fireEvent('autosize', width);
97069                 me.lastInputWidth = width;
97070             }
97071         }
97072     },
97073
97074     initAria: function() {
97075         this.callParent();
97076         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
97077     },
97078
97079     /**
97080      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
97081      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
97082      * performance. Overrides Labelable method.
97083      * @protected
97084      */
97085     getBodyNaturalWidth: function() {
97086         return Math.round(this.size * 6.5) + 20;
97087     }
97088
97089 });
97090
97091 /**
97092  * @docauthor Robert Dougan <rob@sencha.com>
97093  *
97094  * This class creates a multiline text field, which can be used as a direct replacement for traditional
97095  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
97096  * fit its content.
97097  *
97098  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
97099  *
97100  * Example usage:
97101  *
97102  *     @example
97103  *     Ext.create('Ext.form.FormPanel', {
97104  *         title      : 'Sample TextArea',
97105  *         width      : 400,
97106  *         bodyPadding: 10,
97107  *         renderTo   : Ext.getBody(),
97108  *         items: [{
97109  *             xtype     : 'textareafield',
97110  *             grow      : true,
97111  *             name      : 'message',
97112  *             fieldLabel: 'Message',
97113  *             anchor    : '100%'
97114  *         }]
97115  *     });
97116  *
97117  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
97118  * These allow you to set the minimum and maximum grow heights for the textarea.
97119  */
97120 Ext.define('Ext.form.field.TextArea', {
97121     extend:'Ext.form.field.Text',
97122     alias: ['widget.textareafield', 'widget.textarea'],
97123     alternateClassName: 'Ext.form.TextArea',
97124     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
97125
97126     fieldSubTpl: [
97127         '<textarea id="{id}" ',
97128             '<tpl if="name">name="{name}" </tpl>',
97129             '<tpl if="rows">rows="{rows}" </tpl>',
97130             '<tpl if="cols">cols="{cols}" </tpl>',
97131             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97132             'class="{fieldCls} {typeCls}" ',
97133             'autocomplete="off">',
97134         '</textarea>',
97135         {
97136             compiled: true,
97137             disableFormats: true
97138         }
97139     ],
97140
97141     /**
97142      * @cfg {Number} growMin
97143      * The minimum height to allow when {@link #grow}=true
97144      */
97145     growMin: 60,
97146
97147     /**
97148      * @cfg {Number} growMax
97149      * The maximum height to allow when {@link #grow}=true
97150      */
97151     growMax: 1000,
97152
97153     /**
97154      * @cfg {String} growAppend
97155      * A string that will be appended to the field's current value for the purposes of calculating the target field
97156      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
97157      * always a space below the current line.
97158      */
97159     growAppend: '\n-',
97160
97161     /**
97162      * @cfg {Number} cols
97163      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97164      * configured {@link #width} and is not given a width by its container's layout.
97165      */
97166     cols: 20,
97167
97168     /**
97169      * @cfg {Number} cols
97170      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97171      * configured {@link #width} and is not given a width by its container's layout.
97172      */
97173     rows: 4,
97174
97175     /**
97176      * @cfg {Boolean} enterIsSpecial
97177      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
97178      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
97179      * textarea.
97180      */
97181     enterIsSpecial: false,
97182
97183     /**
97184      * @cfg {Boolean} preventScrollbars
97185      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
97186      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
97187      */
97188     preventScrollbars: false,
97189
97190     // private
97191     componentLayout: 'textareafield',
97192
97193     // private
97194     onRender: function(ct, position) {
97195         var me = this;
97196         Ext.applyIf(me.subTplData, {
97197             cols: me.cols,
97198             rows: me.rows
97199         });
97200
97201         me.callParent(arguments);
97202     },
97203
97204     // private
97205     afterRender: function(){
97206         var me = this;
97207
97208         me.callParent(arguments);
97209
97210         if (me.grow) {
97211             if (me.preventScrollbars) {
97212                 me.inputEl.setStyle('overflow', 'hidden');
97213             }
97214             me.inputEl.setHeight(me.growMin);
97215         }
97216     },
97217
97218     // private
97219     fireKey: function(e) {
97220         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
97221             this.fireEvent('specialkey', this, e);
97222         }
97223     },
97224
97225     /**
97226      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
97227      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
97228      */
97229     autoSize: function() {
97230         var me = this,
97231             height;
97232
97233         if (me.grow && me.rendered) {
97234             me.doComponentLayout();
97235             height = me.inputEl.getHeight();
97236             if (height !== me.lastInputHeight) {
97237                 me.fireEvent('autosize', height);
97238                 me.lastInputHeight = height;
97239             }
97240         }
97241     },
97242
97243     // private
97244     initAria: function() {
97245         this.callParent(arguments);
97246         this.getActionEl().dom.setAttribute('aria-multiline', true);
97247     },
97248
97249     /**
97250      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
97251      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
97252      * would hurt performance. Overrides Labelable method.
97253      * @protected
97254      */
97255     getBodyNaturalWidth: function() {
97256         return Math.round(this.cols * 6.5) + 20;
97257     }
97258
97259 });
97260
97261
97262 /**
97263  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
97264  * alias `Ext.Msg` can also be used.
97265  *
97266  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
97267  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
97268  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
97269  * (see the `function` parameter for {@link #show} for more details).
97270  *
97271  * Basic alert
97272  *
97273  *     @example
97274  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
97275  *
97276  * Prompt for user data and process the result using a callback
97277  *
97278  *     @example
97279  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
97280  *         if (btn == 'ok'){
97281  *             // process text value and close...
97282  *         }
97283  *     });
97284  *
97285  * Show a dialog using config options
97286  *
97287  *     @example
97288  *     Ext.Msg.show({
97289  *          title:'Save Changes?',
97290  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
97291  *          buttons: Ext.Msg.YESNOCANCEL,
97292  *          icon: Ext.Msg.QUESTION
97293  *     });
97294  */
97295 Ext.define('Ext.window.MessageBox', {
97296     extend: 'Ext.window.Window',
97297
97298     requires: [
97299         'Ext.toolbar.Toolbar',
97300         'Ext.form.field.Text',
97301         'Ext.form.field.TextArea',
97302         'Ext.button.Button',
97303         'Ext.layout.container.Anchor',
97304         'Ext.layout.container.HBox',
97305         'Ext.ProgressBar'
97306     ],
97307
97308     alias: 'widget.messagebox',
97309
97310     /**
97311      * Button config that displays a single OK button
97312      * @type Number
97313      */
97314     OK : 1,
97315     /**
97316      * Button config that displays a single Yes button
97317      * @type Number
97318      */
97319     YES : 2,
97320     /**
97321      * Button config that displays a single No button
97322      * @type Number
97323      */
97324     NO : 4,
97325     /**
97326      * Button config that displays a single Cancel button
97327      * @type Number
97328      */
97329     CANCEL : 8,
97330     /**
97331      * Button config that displays OK and Cancel buttons
97332      * @type Number
97333      */
97334     OKCANCEL : 9,
97335     /**
97336      * Button config that displays Yes and No buttons
97337      * @type Number
97338      */
97339     YESNO : 6,
97340     /**
97341      * Button config that displays Yes, No and Cancel buttons
97342      * @type Number
97343      */
97344     YESNOCANCEL : 14,
97345     /**
97346      * The CSS class that provides the INFO icon image
97347      * @type String
97348      */
97349     INFO : 'ext-mb-info',
97350     /**
97351      * The CSS class that provides the WARNING icon image
97352      * @type String
97353      */
97354     WARNING : 'ext-mb-warning',
97355     /**
97356      * The CSS class that provides the QUESTION icon image
97357      * @type String
97358      */
97359     QUESTION : 'ext-mb-question',
97360     /**
97361      * The CSS class that provides the ERROR icon image
97362      * @type String
97363      */
97364     ERROR : 'ext-mb-error',
97365
97366     // hide it by offsets. Windows are hidden on render by default.
97367     hideMode: 'offsets',
97368     closeAction: 'hide',
97369     resizable: false,
97370     title: '&#160;',
97371
97372     width: 600,
97373     height: 500,
97374     minWidth: 250,
97375     maxWidth: 600,
97376     minHeight: 110,
97377     maxHeight: 500,
97378     constrain: true,
97379
97380     cls: Ext.baseCSSPrefix + 'message-box',
97381
97382     layout: {
97383         type: 'anchor'
97384     },
97385
97386     /**
97387      * The default height in pixels of the message box's multiline textarea if displayed.
97388      * @type Number
97389      */
97390     defaultTextHeight : 75,
97391     /**
97392      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
97393      * for setting a different minimum width than text-only dialogs may need.
97394      * @type Number
97395      */
97396     minProgressWidth : 250,
97397     /**
97398      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
97399      * for setting a different minimum width than text-only dialogs may need.
97400      * @type Number
97401      */
97402     minPromptWidth: 250,
97403     /**
97404      * An object containing the default button text strings that can be overriden for localized language support.
97405      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
97406      * resource file for handling language support across the framework.
97407      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
97408      * @type Object
97409      */
97410     buttonText: {
97411         ok: 'OK',
97412         yes: 'Yes',
97413         no: 'No',
97414         cancel: 'Cancel'
97415     },
97416
97417     buttonIds: [
97418         'ok', 'yes', 'no', 'cancel'
97419     ],
97420
97421     titleText: {
97422         confirm: 'Confirm',
97423         prompt: 'Prompt',
97424         wait: 'Loading...',
97425         alert: 'Attention'
97426     },
97427
97428     iconHeight: 35,
97429
97430     makeButton: function(btnIdx) {
97431         var btnId = this.buttonIds[btnIdx];
97432         return Ext.create('Ext.button.Button', {
97433             handler: this.btnCallback,
97434             itemId: btnId,
97435             scope: this,
97436             text: this.buttonText[btnId],
97437             minWidth: 75
97438         });
97439     },
97440
97441     btnCallback: function(btn) {
97442         var me = this,
97443             value,
97444             field;
97445
97446         if (me.cfg.prompt || me.cfg.multiline) {
97447             if (me.cfg.multiline) {
97448                 field = me.textArea;
97449             } else {
97450                 field = me.textField;
97451             }
97452             value = field.getValue();
97453             field.reset();
97454         }
97455
97456         // Important not to have focus remain in the hidden Window; Interferes with DnD.
97457         btn.blur();
97458         me.hide();
97459         me.userCallback(btn.itemId, value, me.cfg);
97460     },
97461
97462     hide: function() {
97463         var me = this;
97464         me.dd.endDrag();
97465         me.progressBar.reset();
97466         me.removeCls(me.cfg.cls);
97467         me.callParent();
97468     },
97469
97470     initComponent: function() {
97471         var me = this,
97472             i, button;
97473
97474         me.title = '&#160;';
97475
97476         me.topContainer = Ext.create('Ext.container.Container', {
97477             anchor: '100%',
97478             style: {
97479                 padding: '10px',
97480                 overflow: 'hidden'
97481             },
97482             items: [
97483                 me.iconComponent = Ext.create('Ext.Component', {
97484                     cls: 'ext-mb-icon',
97485                     width: 50,
97486                     height: me.iconHeight,
97487                     style: {
97488                         'float': 'left'
97489                     }
97490                 }),
97491                 me.promptContainer = Ext.create('Ext.container.Container', {
97492                     layout: {
97493                         type: 'anchor'
97494                     },
97495                     items: [
97496                         me.msg = Ext.create('Ext.Component', {
97497                             autoEl: { tag: 'span' },
97498                             cls: 'ext-mb-text'
97499                         }),
97500                         me.textField = Ext.create('Ext.form.field.Text', {
97501                             anchor: '100%',
97502                             enableKeyEvents: true,
97503                             listeners: {
97504                                 keydown: me.onPromptKey,
97505                                 scope: me
97506                             }
97507                         }),
97508                         me.textArea = Ext.create('Ext.form.field.TextArea', {
97509                             anchor: '100%',
97510                             height: 75
97511                         })
97512                     ]
97513                 })
97514             ]
97515         });
97516         me.progressBar = Ext.create('Ext.ProgressBar', {
97517             anchor: '-10',
97518             style: 'margin-left:10px'
97519         });
97520
97521         me.items = [me.topContainer, me.progressBar];
97522
97523         // Create the buttons based upon passed bitwise config
97524         me.msgButtons = [];
97525         for (i = 0; i < 4; i++) {
97526             button = me.makeButton(i);
97527             me.msgButtons[button.itemId] = button;
97528             me.msgButtons.push(button);
97529         }
97530         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
97531             ui: 'footer',
97532             dock: 'bottom',
97533             layout: {
97534                 pack: 'center'
97535             },
97536             items: [
97537                 me.msgButtons[0],
97538                 me.msgButtons[1],
97539                 me.msgButtons[2],
97540                 me.msgButtons[3]
97541             ]
97542         });
97543         me.dockedItems = [me.bottomTb];
97544
97545         me.callParent();
97546     },
97547
97548     onPromptKey: function(textField, e) {
97549         var me = this,
97550             blur;
97551
97552         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
97553             if (me.msgButtons.ok.isVisible()) {
97554                 blur = true;
97555                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
97556             } else if (me.msgButtons.yes.isVisible()) {
97557                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
97558                 blur = true;
97559             }
97560
97561             if (blur) {
97562                 me.textField.blur();
97563             }
97564         }
97565     },
97566
97567     reconfigure: function(cfg) {
97568         var me = this,
97569             buttons = cfg.buttons || 0,
97570             hideToolbar = true,
97571             initialWidth = me.maxWidth,
97572             i;
97573
97574         cfg = cfg || {};
97575         me.cfg = cfg;
97576         if (cfg.width) {
97577             initialWidth = cfg.width;
97578         }
97579
97580         // Default to allowing the Window to take focus.
97581         delete me.defaultFocus;
97582
97583         // clear any old animateTarget
97584         me.animateTarget = cfg.animateTarget || undefined;
97585
97586         // Defaults to modal
97587         me.modal = cfg.modal !== false;
97588
97589         // Show the title
97590         if (cfg.title) {
97591             me.setTitle(cfg.title||'&#160;');
97592         }
97593
97594         if (!me.rendered) {
97595             me.width = initialWidth;
97596             me.render(Ext.getBody());
97597         } else {
97598             me.setSize(initialWidth, me.maxHeight);
97599         }
97600         me.setPosition(-10000, -10000);
97601
97602         // Hide or show the close tool
97603         me.closable = cfg.closable && !cfg.wait;
97604         me.header.child('[type=close]').setVisible(cfg.closable !== false);
97605
97606         // Hide or show the header
97607         if (!cfg.title && !me.closable) {
97608             me.header.hide();
97609         } else {
97610             me.header.show();
97611         }
97612
97613         // Default to dynamic drag: drag the window, not a ghost
97614         me.liveDrag = !cfg.proxyDrag;
97615
97616         // wrap the user callback
97617         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
97618
97619         // Hide or show the icon Component
97620         me.setIcon(cfg.icon);
97621
97622         // Hide or show the message area
97623         if (cfg.msg) {
97624             me.msg.update(cfg.msg);
97625             me.msg.show();
97626         } else {
97627             me.msg.hide();
97628         }
97629
97630         // Hide or show the input field
97631         if (cfg.prompt || cfg.multiline) {
97632             me.multiline = cfg.multiline;
97633             if (cfg.multiline) {
97634                 me.textArea.setValue(cfg.value);
97635                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
97636                 me.textArea.show();
97637                 me.textField.hide();
97638                 me.defaultFocus = me.textArea;
97639             } else {
97640                 me.textField.setValue(cfg.value);
97641                 me.textArea.hide();
97642                 me.textField.show();
97643                 me.defaultFocus = me.textField;
97644             }
97645         } else {
97646             me.textArea.hide();
97647             me.textField.hide();
97648         }
97649
97650         // Hide or show the progress bar
97651         if (cfg.progress || cfg.wait) {
97652             me.progressBar.show();
97653             me.updateProgress(0, cfg.progressText);
97654             if(cfg.wait === true){
97655                 me.progressBar.wait(cfg.waitConfig);
97656             }
97657         } else {
97658             me.progressBar.hide();
97659         }
97660
97661         // Hide or show buttons depending on flag value sent.
97662         for (i = 0; i < 4; i++) {
97663             if (buttons & Math.pow(2, i)) {
97664
97665                 // Default to focus on the first visible button if focus not already set
97666                 if (!me.defaultFocus) {
97667                     me.defaultFocus = me.msgButtons[i];
97668                 }
97669                 me.msgButtons[i].show();
97670                 hideToolbar = false;
97671             } else {
97672                 me.msgButtons[i].hide();
97673             }
97674         }
97675
97676         // Hide toolbar if no buttons to show
97677         if (hideToolbar) {
97678             me.bottomTb.hide();
97679         } else {
97680             me.bottomTb.show();
97681         }
97682     },
97683
97684     /**
97685      * Displays a new message box, or reinitializes an existing message box, based on the config options
97686      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
97687      * although those calls are basic shortcuts and do not support all of the config options allowed here.
97688      * @param {Object} config The following config options are supported: <ul>
97689      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
97690      * opens and closes (defaults to undefined)</div></li>
97691      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
97692      * <li>Ext.window.MessageBox.OK</li>
97693      * <li>Ext.window.MessageBox.YES</li>
97694      * <li>Ext.window.MessageBox.NO</li>
97695      * <li>Ext.window.MessageBox.CANCEL</li>
97696      * </ul>Or false to not show any buttons (defaults to false)</div></li>
97697      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
97698      * progress and wait dialogs will ignore this property and always hide the close button as they can only
97699      * be closed programmatically.</div></li>
97700      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
97701      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
97702      * if displayed (defaults to 75)</div></li>
97703      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
97704      * by clicking on the configured buttons, or on the dialog close button, or by pressing
97705      * the return button to enter input.
97706      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
97707      * actions and can only be closed programmatically, so any required function should be called
97708      * by the same code after it closes the dialog. Parameters passed:<ul>
97709      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
97710      * <li><tt>ok</tt></li>
97711      * <li><tt>yes</tt></li>
97712      * <li><tt>no</tt></li>
97713      * <li><tt>cancel</tt></li>
97714      * </ul></div></div></li>
97715      * <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>
97716      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
97717      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
97718      * </ul></p></div></li>
97719      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
97720      * <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
97721      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
97722      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
97723      * add an optional header icon (defaults to '')</div></li>
97724      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
97725      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
97726      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
97727      * displayed (defaults to true)</div></li>
97728      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
97729      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
97730      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
97731      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
97732      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97733      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
97734      * <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>
97735      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
97736      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
97737      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
97738      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97739      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
97740      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
97741      * </ul>
97742      * Example usage:
97743      * <pre><code>
97744 Ext.Msg.show({
97745 title: 'Address',
97746 msg: 'Please enter your address:',
97747 width: 300,
97748 buttons: Ext.Msg.OKCANCEL,
97749 multiline: true,
97750 fn: saveAddress,
97751 animateTarget: 'addAddressBtn',
97752 icon: Ext.window.MessageBox.INFO
97753 });
97754 </code></pre>
97755      * @return {Ext.window.MessageBox} this
97756      */
97757     show: function(cfg) {
97758         var me = this;
97759
97760         me.reconfigure(cfg);
97761         me.addCls(cfg.cls);
97762         if (cfg.animateTarget) {
97763             me.doAutoSize(true);
97764             me.callParent();
97765         } else {
97766             me.callParent();
97767             me.doAutoSize(true);
97768         }
97769         return me;
97770     },
97771
97772     afterShow: function(){
97773         if (this.animateTarget) {
97774             this.center();
97775         }
97776         this.callParent(arguments);
97777     },
97778
97779     doAutoSize: function(center) {
97780         var me = this,
97781             icon = me.iconComponent,
97782             iconHeight = me.iconHeight;
97783
97784         if (!Ext.isDefined(me.frameWidth)) {
97785             me.frameWidth = me.el.getWidth() - me.body.getWidth();
97786         }
97787
97788         // reset to the original dimensions
97789         icon.setHeight(iconHeight);
97790
97791         // Allow per-invocation override of minWidth
97792         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
97793
97794         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
97795         // then constraining it to within the max with. Then adding up constituent element heights.
97796         me.topContainer.doLayout();
97797         if (Ext.isIE6 || Ext.isIEQuirks) {
97798             // In IE quirks, the initial full width of the prompt fields will prevent the container element
97799             // from collapsing once sized down, so temporarily force them to a small width. They'll get
97800             // layed out to their final width later when setting the final window size.
97801             me.textField.setCalculatedSize(9);
97802             me.textArea.setCalculatedSize(9);
97803         }
97804         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
97805             height = (me.header.rendered ? me.header.getHeight() : 0) +
97806             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
97807             me.progressBar.getHeight() +
97808             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
97809
97810         // Update to the size of the content, this way the text won't wrap under the icon.
97811         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
97812         me.setSize(width + me.frameWidth, height + me.frameWidth);
97813         if (center) {
97814             me.center();
97815         }
97816         return me;
97817     },
97818
97819     updateText: function(text) {
97820         this.msg.update(text);
97821         return this.doAutoSize(true);
97822     },
97823
97824     /**
97825      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
97826      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
97827      * to clear any existing icon. This method must be called before the MessageBox is shown.
97828      * The following built-in icon classes are supported, but you can also pass in a custom class name:
97829      * <pre>
97830 Ext.window.MessageBox.INFO
97831 Ext.window.MessageBox.WARNING
97832 Ext.window.MessageBox.QUESTION
97833 Ext.window.MessageBox.ERROR
97834      *</pre>
97835      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
97836      * @return {Ext.window.MessageBox} this
97837      */
97838     setIcon : function(icon) {
97839         var me = this;
97840         me.iconComponent.removeCls(me.iconCls);
97841         if (icon) {
97842             me.iconComponent.show();
97843             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
97844             me.iconComponent.addCls(me.iconCls = icon);
97845         } else {
97846             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
97847             me.iconComponent.hide();
97848         }
97849         return me;
97850     },
97851
97852     /**
97853      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
97854      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
97855      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
97856      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
97857      * @param {String} [progressText=''] The progress text to display inside the progress bar.
97858      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
97859      * so that any existing body text will not get overwritten by default unless a new value is passed in)
97860      * @return {Ext.window.MessageBox} this
97861      */
97862     updateProgress : function(value, progressText, msg){
97863         this.progressBar.updateProgress(value, progressText);
97864         if (msg){
97865             this.updateText(msg);
97866         }
97867         return this;
97868     },
97869
97870     onEsc: function() {
97871         if (this.closable !== false) {
97872             this.callParent(arguments);
97873         }
97874     },
97875
97876     /**
97877      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
97878      * If a callback function is passed it will be called after the user clicks either button,
97879      * and the id of the button that was clicked will be passed as the only parameter to the callback
97880      * (could also be the top-right close button).
97881      * @param {String} title The title bar text
97882      * @param {String} msg The message box body text
97883      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97884      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97885      * @return {Ext.window.MessageBox} this
97886      */
97887     confirm: function(cfg, msg, fn, scope) {
97888         if (Ext.isString(cfg)) {
97889             cfg = {
97890                 title: cfg,
97891                 icon: 'ext-mb-question',
97892                 msg: msg,
97893                 buttons: this.YESNO,
97894                 callback: fn,
97895                 scope: scope
97896             };
97897         }
97898         return this.show(cfg);
97899     },
97900
97901     /**
97902      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
97903      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
97904      * clicks either button, and the id of the button that was clicked (could also be the top-right
97905      * close button) and the text that was entered will be passed as the two parameters to the callback.
97906      * @param {String} title The title bar text
97907      * @param {String} msg The message box body text
97908      * @param {Function} [fn] The callback function invoked after the message box is closed
97909      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97910      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
97911      * property, or the height in pixels to create the textbox/
97912      * @param {String} [value=''] Default value of the text input element
97913      * @return {Ext.window.MessageBox} this
97914      */
97915     prompt : function(cfg, msg, fn, scope, multiline, value){
97916         if (Ext.isString(cfg)) {
97917             cfg = {
97918                 prompt: true,
97919                 title: cfg,
97920                 minWidth: this.minPromptWidth,
97921                 msg: msg,
97922                 buttons: this.OKCANCEL,
97923                 callback: fn,
97924                 scope: scope,
97925                 multiline: multiline,
97926                 value: value
97927             };
97928         }
97929         return this.show(cfg);
97930     },
97931
97932     /**
97933      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
97934      * interaction while waiting for a long-running process to complete that does not have defined intervals.
97935      * You are responsible for closing the message box when the process is complete.
97936      * @param {String} msg The message box body text
97937      * @param {String} title (optional) The title bar text
97938      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
97939      * @return {Ext.window.MessageBox} this
97940      */
97941     wait : function(cfg, title, config){
97942         if (Ext.isString(cfg)) {
97943             cfg = {
97944                 title : title,
97945                 msg : cfg,
97946                 closable: false,
97947                 wait: true,
97948                 modal: true,
97949                 minWidth: this.minProgressWidth,
97950                 waitConfig: config
97951             };
97952         }
97953         return this.show(cfg);
97954     },
97955
97956     /**
97957      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
97958      * If a callback function is passed it will be called after the user clicks the button, and the
97959      * id of the button that was clicked will be passed as the only parameter to the callback
97960      * (could also be the top-right close button).
97961      * @param {String} title The title bar text
97962      * @param {String} msg The message box body text
97963      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97964      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97965      * @return {Ext.window.MessageBox} this
97966      */
97967     alert: function(cfg, msg, fn, scope) {
97968         if (Ext.isString(cfg)) {
97969             cfg = {
97970                 title : cfg,
97971                 msg : msg,
97972                 buttons: this.OK,
97973                 fn: fn,
97974                 scope : scope,
97975                 minWidth: this.minWidth
97976             };
97977         }
97978         return this.show(cfg);
97979     },
97980
97981     /**
97982      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
97983      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
97984      * and closing the message box when the process is complete.
97985      * @param {String} title The title bar text
97986      * @param {String} msg The message box body text
97987      * @param {String} [progressText=''] The text to display inside the progress bar
97988      * @return {Ext.window.MessageBox} this
97989      */
97990     progress : function(cfg, msg, progressText){
97991         if (Ext.isString(cfg)) {
97992             cfg = {
97993                 title: cfg,
97994                 msg: msg,
97995                 progress: true,
97996                 progressText: progressText
97997             };
97998         }
97999         return this.show(cfg);
98000     }
98001 }, function() {
98002     /**
98003      * @class Ext.MessageBox
98004      * @alternateClassName Ext.Msg
98005      * @extends Ext.window.MessageBox
98006      * @singleton
98007      * Singleton instance of {@link Ext.window.MessageBox}.
98008      */
98009     Ext.MessageBox = Ext.Msg = new this();
98010 });
98011 /**
98012  * @class Ext.form.Basic
98013  * @extends Ext.util.Observable
98014  *
98015  * Provides input field management, validation, submission, and form loading services for the collection
98016  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
98017  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
98018  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
98019  *
98020  * ## Form Actions
98021  *
98022  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
98023  * See the various Action implementations for specific details of each one's functionality, as well as the
98024  * documentation for {@link #doAction} which details the configuration options that can be specified in
98025  * each action call.
98026  *
98027  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
98028  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
98029  * {@link #standardSubmit} config option.
98030  *
98031  * ## File uploads
98032  *
98033  * File uploads are not performed using normal 'Ajax' techniques; see the description for
98034  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
98035  *
98036  * ## Example usage:
98037  *
98038  *     Ext.create('Ext.form.Panel', {
98039  *         title: 'Basic Form',
98040  *         renderTo: Ext.getBody(),
98041  *         bodyPadding: 5,
98042  *         width: 350,
98043  *
98044  *         // Any configuration items here will be automatically passed along to
98045  *         // the Ext.form.Basic instance when it gets created.
98046  *
98047  *         // The form will submit an AJAX request to this URL when submitted
98048  *         url: 'save-form.php',
98049  *
98050  *         items: [{
98051  *             fieldLabel: 'Field',
98052  *             name: 'theField'
98053  *         }],
98054  *
98055  *         buttons: [{
98056  *             text: 'Submit',
98057  *             handler: function() {
98058  *                 // The getForm() method returns the Ext.form.Basic instance:
98059  *                 var form = this.up('form').getForm();
98060  *                 if (form.isValid()) {
98061  *                     // Submit the Ajax request and handle the response
98062  *                     form.submit({
98063  *                         success: function(form, action) {
98064  *                            Ext.Msg.alert('Success', action.result.msg);
98065  *                         },
98066  *                         failure: function(form, action) {
98067  *                             Ext.Msg.alert('Failed', action.result.msg);
98068  *                         }
98069  *                     });
98070  *                 }
98071  *             }
98072  *         }]
98073  *     });
98074  *
98075  * @docauthor Jason Johnston <jason@sencha.com>
98076  */
98077 Ext.define('Ext.form.Basic', {
98078     extend: 'Ext.util.Observable',
98079     alternateClassName: 'Ext.form.BasicForm',
98080     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
98081                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
98082
98083     /**
98084      * Creates new form.
98085      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
98086      * @param {Object} config Configuration options. These are normally specified in the config to the
98087      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
98088      */
98089     constructor: function(owner, config) {
98090         var me = this,
98091             onItemAddOrRemove = me.onItemAddOrRemove;
98092
98093         /**
98094          * @property owner
98095          * @type Ext.container.Container
98096          * The container component to which this BasicForm is attached.
98097          */
98098         me.owner = owner;
98099
98100         // Listen for addition/removal of fields in the owner container
98101         me.mon(owner, {
98102             add: onItemAddOrRemove,
98103             remove: onItemAddOrRemove,
98104             scope: me
98105         });
98106
98107         Ext.apply(me, config);
98108
98109         // Normalize the paramOrder to an Array
98110         if (Ext.isString(me.paramOrder)) {
98111             me.paramOrder = me.paramOrder.split(/[\s,|]/);
98112         }
98113
98114         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
98115
98116         me.addEvents(
98117             /**
98118              * @event beforeaction
98119              * Fires before any action is performed. Return false to cancel the action.
98120              * @param {Ext.form.Basic} this
98121              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
98122              */
98123             'beforeaction',
98124             /**
98125              * @event actionfailed
98126              * Fires when an action fails.
98127              * @param {Ext.form.Basic} this
98128              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
98129              */
98130             'actionfailed',
98131             /**
98132              * @event actioncomplete
98133              * Fires when an action is completed.
98134              * @param {Ext.form.Basic} this
98135              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
98136              */
98137             'actioncomplete',
98138             /**
98139              * @event validitychange
98140              * Fires when the validity of the entire form changes.
98141              * @param {Ext.form.Basic} this
98142              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
98143              */
98144             'validitychange',
98145             /**
98146              * @event dirtychange
98147              * Fires when the dirty state of the entire form changes.
98148              * @param {Ext.form.Basic} this
98149              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
98150              */
98151             'dirtychange'
98152         );
98153         me.callParent();
98154     },
98155
98156     /**
98157      * Do any post constructor initialization
98158      * @private
98159      */
98160     initialize: function(){
98161         this.initialized = true;
98162         this.onValidityChange(!this.hasInvalidField());
98163     },
98164
98165     /**
98166      * @cfg {String} method
98167      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
98168      */
98169
98170     /**
98171      * @cfg {Ext.data.reader.Reader} reader
98172      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
98173      * data when executing 'load' actions. This is optional as there is built-in
98174      * support for processing JSON responses.
98175      */
98176
98177     /**
98178      * @cfg {Ext.data.reader.Reader} errorReader
98179      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
98180      * read field error messages returned from 'submit' actions. This is optional
98181      * as there is built-in support for processing JSON responses.</p>
98182      * <p>The Records which provide messages for the invalid Fields must use the
98183      * Field name (or id) as the Record ID, and must contain a field called 'msg'
98184      * which contains the error message.</p>
98185      * <p>The errorReader does not have to be a full-blown implementation of a
98186      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
98187      * which returns an Array of Records in an object with the following
98188      * structure:</p><pre><code>
98189 {
98190     records: recordArray
98191 }
98192 </code></pre>
98193      */
98194
98195     /**
98196      * @cfg {String} url
98197      * The URL to use for form actions if one isn't supplied in the
98198      * {@link #doAction doAction} options.
98199      */
98200
98201     /**
98202      * @cfg {Object} baseParams
98203      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
98204      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
98205      */
98206
98207     /**
98208      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
98209      */
98210     timeout: 30,
98211
98212     /**
98213      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
98214      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
98215      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
98216      * forms.
98217      * Such as the following:<pre><code>
98218 api: {
98219     load: App.ss.MyProfile.load,
98220     submit: App.ss.MyProfile.submit
98221 }
98222 </code></pre>
98223      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
98224      * to customize how the load method is invoked.
98225      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
98226      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
98227      */
98228
98229     /**
98230      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
98231      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
98232      * <code>load</code> configuration.</p>
98233      * <p>Specify the params in the order in which they must be executed on the
98234      * server-side as either (1) an Array of String values, or (2) a String of params
98235      * delimited by either whitespace, comma, or pipe. For example,
98236      * any of the following would be acceptable:</p><pre><code>
98237 paramOrder: ['param1','param2','param3']
98238 paramOrder: 'param1 param2 param3'
98239 paramOrder: 'param1,param2,param3'
98240 paramOrder: 'param1|param2|param'
98241      </code></pre>
98242      */
98243
98244     /**
98245      * @cfg {Boolean} paramsAsHash
98246      * Only used for the <code>{@link #api}</code>
98247      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
98248      * single hash collection of named arguments. Providing a
98249      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
98250      */
98251     paramsAsHash: false,
98252
98253     /**
98254      * @cfg {String} waitTitle
98255      * The default title to show for the waiting message box
98256      */
98257     waitTitle: 'Please Wait...',
98258
98259     /**
98260      * @cfg {Boolean} trackResetOnLoad
98261      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
98262      * when the form was first created.
98263      */
98264     trackResetOnLoad: false,
98265
98266     /**
98267      * @cfg {Boolean} standardSubmit
98268      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
98269      * All of the field values, plus any additional params configured via {@link #baseParams}
98270      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
98271      */
98272
98273     /**
98274      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
98275      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
98276      * element by passing it or its id or mask the form itself by passing in true.
98277      */
98278
98279
98280     // Private
98281     wasDirty: false,
98282
98283
98284     /**
98285      * Destroys this object.
98286      */
98287     destroy: function() {
98288         this.clearListeners();
98289         this.checkValidityTask.cancel();
98290     },
98291
98292     /**
98293      * @private
98294      * Handle addition or removal of descendant items. Invalidates the cached list of fields
98295      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
98296      * for state change events on added fields, and tracks components with formBind=true.
98297      */
98298     onItemAddOrRemove: function(parent, child) {
98299         var me = this,
98300             isAdding = !!child.ownerCt,
98301             isContainer = child.isContainer;
98302
98303         function handleField(field) {
98304             // Listen for state change events on fields
98305             me[isAdding ? 'mon' : 'mun'](field, {
98306                 validitychange: me.checkValidity,
98307                 dirtychange: me.checkDirty,
98308                 scope: me,
98309                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
98310             });
98311             // Flush the cached list of fields
98312             delete me._fields;
98313         }
98314
98315         if (child.isFormField) {
98316             handleField(child);
98317         } else if (isContainer) {
98318             // Walk down
98319             if (child.isDestroyed) {
98320                 // the container is destroyed, this means we may have child fields, so here
98321                 // we just invalidate all the fields to be sure.
98322                 delete me._fields;
98323             } else {
98324                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
98325             }
98326         }
98327
98328         // Flush the cached list of formBind components
98329         delete this._boundItems;
98330
98331         // Check form bind, but only after initial add. Batch it to prevent excessive validation
98332         // calls when many fields are being added at once.
98333         if (me.initialized) {
98334             me.checkValidityTask.delay(10);
98335         }
98336     },
98337
98338     /**
98339      * Return all the {@link Ext.form.field.Field} components in the owner container.
98340      * @return {Ext.util.MixedCollection} Collection of the Field objects
98341      */
98342     getFields: function() {
98343         var fields = this._fields;
98344         if (!fields) {
98345             fields = this._fields = Ext.create('Ext.util.MixedCollection');
98346             fields.addAll(this.owner.query('[isFormField]'));
98347         }
98348         return fields;
98349     },
98350
98351     /**
98352      * @private
98353      * Finds and returns the set of all items bound to fields inside this form
98354      * @return {Ext.util.MixedCollection} The set of all bound form field items
98355      */
98356     getBoundItems: function() {
98357         var boundItems = this._boundItems;
98358         
98359         if (!boundItems || boundItems.getCount() === 0) {
98360             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
98361             boundItems.addAll(this.owner.query('[formBind]'));
98362         }
98363         
98364         return boundItems;
98365     },
98366
98367     /**
98368      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
98369      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
98370      */
98371     hasInvalidField: function() {
98372         return !!this.getFields().findBy(function(field) {
98373             var preventMark = field.preventMark,
98374                 isValid;
98375             field.preventMark = true;
98376             isValid = field.isValid();
98377             field.preventMark = preventMark;
98378             return !isValid;
98379         });
98380     },
98381
98382     /**
98383      * Returns true if client-side validation on the form is successful. Any invalid fields will be
98384      * marked as invalid. If you only want to determine overall form validity without marking anything,
98385      * use {@link #hasInvalidField} instead.
98386      * @return Boolean
98387      */
98388     isValid: function() {
98389         var me = this,
98390             invalid;
98391         me.batchLayouts(function() {
98392             invalid = me.getFields().filterBy(function(field) {
98393                 return !field.validate();
98394             });
98395         });
98396         return invalid.length < 1;
98397     },
98398
98399     /**
98400      * Check whether the validity of the entire form has changed since it was last checked, and
98401      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
98402      * when an individual field's validity changes.
98403      */
98404     checkValidity: function() {
98405         var me = this,
98406             valid = !me.hasInvalidField();
98407         if (valid !== me.wasValid) {
98408             me.onValidityChange(valid);
98409             me.fireEvent('validitychange', me, valid);
98410             me.wasValid = valid;
98411         }
98412     },
98413
98414     /**
98415      * @private
98416      * Handle changes in the form's validity. If there are any sub components with
98417      * formBind=true then they are enabled/disabled based on the new validity.
98418      * @param {Boolean} valid
98419      */
98420     onValidityChange: function(valid) {
98421         var boundItems = this.getBoundItems();
98422         if (boundItems) {
98423             boundItems.each(function(cmp) {
98424                 if (cmp.disabled === valid) {
98425                     cmp.setDisabled(!valid);
98426                 }
98427             });
98428         }
98429     },
98430
98431     /**
98432      * <p>Returns true if any fields in this form have changed from their original values.</p>
98433      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
98434      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
98435      * or {@link #loadRecord}.</p>
98436      * @return Boolean
98437      */
98438     isDirty: function() {
98439         return !!this.getFields().findBy(function(f) {
98440             return f.isDirty();
98441         });
98442     },
98443
98444     /**
98445      * Check whether the dirty state of the entire form has changed since it was last checked, and
98446      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
98447      * when an individual field's dirty state changes.
98448      */
98449     checkDirty: function() {
98450         var dirty = this.isDirty();
98451         if (dirty !== this.wasDirty) {
98452             this.fireEvent('dirtychange', this, dirty);
98453             this.wasDirty = dirty;
98454         }
98455     },
98456
98457     /**
98458      * <p>Returns true if the form contains a file upload field. This is used to determine the
98459      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
98460      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
98461      * element containing all the fields is created temporarily and submitted with its
98462      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
98463      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
98464      * but removed after the return data has been gathered.</p>
98465      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
98466      * server is using JSON to send the return object, then the
98467      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
98468      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
98469      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
98470      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
98471      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
98472      * is created containing a <tt>responseText</tt> property in order to conform to the
98473      * requirements of event handlers and callbacks.</p>
98474      * <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>
98475      * and some server technologies (notably JEE) may require some custom processing in order to
98476      * retrieve parameter names and parameter values from the packet content.</p>
98477      * @return Boolean
98478      */
98479     hasUpload: function() {
98480         return !!this.getFields().findBy(function(f) {
98481             return f.isFileUpload();
98482         });
98483     },
98484
98485     /**
98486      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
98487      * to perform application-specific processing.
98488      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
98489      * or instance of {@link Ext.form.action.Action} to perform.
98490      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
98491      * that will get created, if the <tt>action</tt> argument is a String.
98492      * <p>All of the config options listed below are supported by both the
98493      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98494      * actions unless otherwise noted (custom actions could also accept
98495      * other config options):</p><ul>
98496      *
98497      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
98498      * to the form's {@link #url}.)</div></li>
98499      *
98500      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
98501      * to the form's method, or POST if not defined)</div></li>
98502      *
98503      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
98504      * (defaults to the form's baseParams, or none if not defined)</p>
98505      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
98506      *
98507      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
98508      *
98509      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
98510      * be invoked after a successful response (see top of
98511      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98512      * for a description of what constitutes a successful response).
98513      * The function is passed the following parameters:<ul>
98514      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98515      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98516      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98517      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98518      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98519      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98520      * </ul></div></li></ul></div></li>
98521      *
98522      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
98523      * failed transaction attempt. The function is passed the following parameters:<ul>
98524      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98525      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98526      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98527      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
98528      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98529      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98530      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98531      * </ul></div></li></ul></div></li>
98532      *
98533      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
98534      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
98535      *
98536      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
98537      * Determines whether a Form's fields are validated in a final call to
98538      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
98539      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
98540      *
98541      * @return {Ext.form.Basic} this
98542      */
98543     doAction: function(action, options) {
98544         if (Ext.isString(action)) {
98545             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
98546         }
98547         if (this.fireEvent('beforeaction', this, action) !== false) {
98548             this.beforeAction(action);
98549             Ext.defer(action.run, 100, action);
98550         }
98551         return this;
98552     },
98553
98554     /**
98555      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
98556      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
98557      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
98558      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
98559      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
98560      * <p>The following code:</p><pre><code>
98561 myFormPanel.getForm().submit({
98562     clientValidation: true,
98563     url: 'updateConsignment.php',
98564     params: {
98565         newStatus: 'delivered'
98566     },
98567     success: function(form, action) {
98568        Ext.Msg.alert('Success', action.result.msg);
98569     },
98570     failure: function(form, action) {
98571         switch (action.failureType) {
98572             case Ext.form.action.Action.CLIENT_INVALID:
98573                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
98574                 break;
98575             case Ext.form.action.Action.CONNECT_FAILURE:
98576                 Ext.Msg.alert('Failure', 'Ajax communication failed');
98577                 break;
98578             case Ext.form.action.Action.SERVER_INVALID:
98579                Ext.Msg.alert('Failure', action.result.msg);
98580        }
98581     }
98582 });
98583 </code></pre>
98584      * would process the following server response for a successful submission:<pre><code>
98585 {
98586     "success":true, // note this is Boolean, not string
98587     "msg":"Consignment updated"
98588 }
98589 </code></pre>
98590      * and the following server response for a failed submission:<pre><code>
98591 {
98592     "success":false, // note this is Boolean, not string
98593     "msg":"You do not have permission to perform this operation"
98594 }
98595 </code></pre>
98596      * @return {Ext.form.Basic} this
98597      */
98598     submit: function(options) {
98599         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
98600     },
98601
98602     /**
98603      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
98604      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
98605      * @return {Ext.form.Basic} this
98606      */
98607     load: function(options) {
98608         return this.doAction(this.api ? 'directload' : 'load', options);
98609     },
98610
98611     /**
98612      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
98613      * @param {Ext.data.Model} record The record to edit
98614      * @return {Ext.form.Basic} this
98615      */
98616     updateRecord: function(record) {
98617         var fields = record.fields,
98618             values = this.getFieldValues(),
98619             name,
98620             obj = {};
98621
98622         fields.each(function(f) {
98623             name = f.name;
98624             if (name in values) {
98625                 obj[name] = values[name];
98626             }
98627         });
98628
98629         record.beginEdit();
98630         record.set(obj);
98631         record.endEdit();
98632
98633         return this;
98634     },
98635
98636     /**
98637      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
98638      * {@link Ext.data.Model#raw record data}.
98639      * See also {@link #trackResetOnLoad}.
98640      * @param {Ext.data.Model} record The record to load
98641      * @return {Ext.form.Basic} this
98642      */
98643     loadRecord: function(record) {
98644         this._record = record;
98645         return this.setValues(record.data);
98646     },
98647
98648     /**
98649      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
98650      * @return {Ext.data.Model} The record
98651      */
98652     getRecord: function() {
98653         return this._record;
98654     },
98655
98656     /**
98657      * @private
98658      * Called before an action is performed via {@link #doAction}.
98659      * @param {Ext.form.action.Action} action The Action instance that was invoked
98660      */
98661     beforeAction: function(action) {
98662         var waitMsg = action.waitMsg,
98663             maskCls = Ext.baseCSSPrefix + 'mask-loading',
98664             waitMsgTarget;
98665
98666         // Call HtmlEditor's syncValue before actions
98667         this.getFields().each(function(f) {
98668             if (f.isFormField && f.syncValue) {
98669                 f.syncValue();
98670             }
98671         });
98672
98673         if (waitMsg) {
98674             waitMsgTarget = this.waitMsgTarget;
98675             if (waitMsgTarget === true) {
98676                 this.owner.el.mask(waitMsg, maskCls);
98677             } else if (waitMsgTarget) {
98678                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
98679                 waitMsgTarget.mask(waitMsg, maskCls);
98680             } else {
98681                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
98682             }
98683         }
98684     },
98685
98686     /**
98687      * @private
98688      * Called after an action is performed via {@link #doAction}.
98689      * @param {Ext.form.action.Action} action The Action instance that was invoked
98690      * @param {Boolean} success True if the action completed successfully, false, otherwise.
98691      */
98692     afterAction: function(action, success) {
98693         if (action.waitMsg) {
98694             var MessageBox = Ext.MessageBox,
98695                 waitMsgTarget = this.waitMsgTarget;
98696             if (waitMsgTarget === true) {
98697                 this.owner.el.unmask();
98698             } else if (waitMsgTarget) {
98699                 waitMsgTarget.unmask();
98700             } else {
98701                 MessageBox.updateProgress(1);
98702                 MessageBox.hide();
98703             }
98704         }
98705         if (success) {
98706             if (action.reset) {
98707                 this.reset();
98708             }
98709             Ext.callback(action.success, action.scope || action, [this, action]);
98710             this.fireEvent('actioncomplete', this, action);
98711         } else {
98712             Ext.callback(action.failure, action.scope || action, [this, action]);
98713             this.fireEvent('actionfailed', this, action);
98714         }
98715     },
98716
98717
98718     /**
98719      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
98720      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
98721      * {@link Ext.form.field.Field#getName name or hiddenName}).
98722      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
98723      */
98724     findField: function(id) {
98725         return this.getFields().findBy(function(f) {
98726             return f.id === id || f.getName() === id;
98727         });
98728     },
98729
98730
98731     /**
98732      * Mark fields in this form invalid in bulk.
98733      * @param {Object/Object[]/Ext.data.Errors} errors
98734      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
98735      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
98736      * @return {Ext.form.Basic} this
98737      */
98738     markInvalid: function(errors) {
98739         var me = this;
98740
98741         function mark(fieldId, msg) {
98742             var field = me.findField(fieldId);
98743             if (field) {
98744                 field.markInvalid(msg);
98745             }
98746         }
98747
98748         if (Ext.isArray(errors)) {
98749             Ext.each(errors, function(err) {
98750                 mark(err.id, err.msg);
98751             });
98752         }
98753         else if (errors instanceof Ext.data.Errors) {
98754             errors.each(function(err) {
98755                 mark(err.field, err.message);
98756             });
98757         }
98758         else {
98759             Ext.iterate(errors, mark);
98760         }
98761         return this;
98762     },
98763
98764     /**
98765      * Set values for fields in this form in bulk.
98766      * @param {Object/Object[]} values Either an array in the form:<pre><code>
98767 [{id:'clientName', value:'Fred. Olsen Lines'},
98768  {id:'portOfLoading', value:'FXT'},
98769  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
98770      * or an object hash of the form:<pre><code>
98771 {
98772     clientName: 'Fred. Olsen Lines',
98773     portOfLoading: 'FXT',
98774     portOfDischarge: 'OSL'
98775 }</code></pre>
98776      * @return {Ext.form.Basic} this
98777      */
98778     setValues: function(values) {
98779         var me = this;
98780
98781         function setVal(fieldId, val) {
98782             var field = me.findField(fieldId);
98783             if (field) {
98784                 field.setValue(val);
98785                 if (me.trackResetOnLoad) {
98786                     field.resetOriginalValue();
98787                 }
98788             }
98789         }
98790
98791         if (Ext.isArray(values)) {
98792             // array of objects
98793             Ext.each(values, function(val) {
98794                 setVal(val.id, val.value);
98795             });
98796         } else {
98797             // object hash
98798             Ext.iterate(values, setVal);
98799         }
98800         return this;
98801     },
98802
98803     /**
98804      * Retrieves the fields in the form as a set of key/value pairs, using their
98805      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
98806      * If multiple fields return values under the same name those values will be combined into an Array.
98807      * This is similar to {@link #getFieldValues} except that this method collects only String values for
98808      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
98809      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
98810      * URL-encoded param string. Defaults to false.
98811      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98812      * Defaults to false.
98813      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
98814      * Defaults to false.
98815      * @return {String/Object}
98816      */
98817     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
98818         var values = {};
98819
98820         this.getFields().each(function(field) {
98821             if (!dirtyOnly || field.isDirty()) {
98822                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
98823                 if (Ext.isObject(data)) {
98824                     Ext.iterate(data, function(name, val) {
98825                         if (includeEmptyText && val === '') {
98826                             val = field.emptyText || '';
98827                         }
98828                         if (name in values) {
98829                             var bucket = values[name],
98830                                 isArray = Ext.isArray;
98831                             if (!isArray(bucket)) {
98832                                 bucket = values[name] = [bucket];
98833                             }
98834                             if (isArray(val)) {
98835                                 values[name] = bucket.concat(val);
98836                             } else {
98837                                 bucket.push(val);
98838                             }
98839                         } else {
98840                             values[name] = val;
98841                         }
98842                     });
98843                 }
98844             }
98845         });
98846
98847         if (asString) {
98848             values = Ext.Object.toQueryString(values);
98849         }
98850         return values;
98851     },
98852
98853     /**
98854      * Retrieves the fields in the form as a set of key/value pairs, using their
98855      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
98856      * If multiple fields return values under the same name those values will be combined into an Array.
98857      * This is similar to {@link #getValues} except that this method collects type-specific data values
98858      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
98859      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98860      * Defaults to false.
98861      * @return {Object}
98862      */
98863     getFieldValues: function(dirtyOnly) {
98864         return this.getValues(false, dirtyOnly, false, true);
98865     },
98866
98867     /**
98868      * Clears all invalid field messages in this form.
98869      * @return {Ext.form.Basic} this
98870      */
98871     clearInvalid: function() {
98872         var me = this;
98873         me.batchLayouts(function() {
98874             me.getFields().each(function(f) {
98875                 f.clearInvalid();
98876             });
98877         });
98878         return me;
98879     },
98880
98881     /**
98882      * Resets all fields in this form.
98883      * @return {Ext.form.Basic} this
98884      */
98885     reset: function() {
98886         var me = this;
98887         me.batchLayouts(function() {
98888             me.getFields().each(function(f) {
98889                 f.reset();
98890             });
98891         });
98892         return me;
98893     },
98894
98895     /**
98896      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
98897      * @param {Object} obj The object to be applied
98898      * @return {Ext.form.Basic} this
98899      */
98900     applyToFields: function(obj) {
98901         this.getFields().each(function(f) {
98902             Ext.apply(f, obj);
98903         });
98904         return this;
98905     },
98906
98907     /**
98908      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
98909      * @param {Object} obj The object to be applied
98910      * @return {Ext.form.Basic} this
98911      */
98912     applyIfToFields: function(obj) {
98913         this.getFields().each(function(f) {
98914             Ext.applyIf(f, obj);
98915         });
98916         return this;
98917     },
98918
98919     /**
98920      * @private
98921      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
98922      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
98923      * @param {Function} fn
98924      */
98925     batchLayouts: function(fn) {
98926         var me = this,
98927             suspended = new Ext.util.HashMap();
98928
98929         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
98930         me.getFields().each(function(field) {
98931             var ownerCt = field.ownerCt;
98932             if (!suspended.contains(ownerCt)) {
98933                 suspended.add(ownerCt);
98934                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
98935                 ownerCt.suspendLayout = true;
98936             }
98937         });
98938
98939         // Invoke the function
98940         fn();
98941
98942         // Un-suspend the container layouts
98943         suspended.each(function(id, ct) {
98944             ct.suspendLayout = ct.oldSuspendLayout;
98945             delete ct.oldSuspendLayout;
98946         });
98947
98948         // Trigger a single layout
98949         me.owner.doComponentLayout();
98950     }
98951 });
98952
98953 /**
98954  * @class Ext.form.FieldAncestor
98955
98956 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
98957 items subtree. Adds the following capabilities:
98958
98959 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
98960   instances at any depth within the container.
98961 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
98962   of individual fields at the container level.
98963 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
98964   container, to facilitate uniform configuration of all fields.
98965
98966 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
98967 and should not normally need to be used directly.
98968
98969  * @markdown
98970  * @docauthor Jason Johnston <jason@sencha.com>
98971  */
98972 Ext.define('Ext.form.FieldAncestor', {
98973
98974     /**
98975      * @cfg {Object} fieldDefaults
98976      * <p>If specified, the properties in this object are used as default config values for each
98977      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
98978      * that is added as a descendant of this container. Corresponding values specified in an individual field's
98979      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
98980      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
98981      * options may be specified in the <tt>fieldDefaults</tt>.</p>
98982      * <p>Example:</p>
98983      * <pre><code>new Ext.form.Panel({
98984     fieldDefaults: {
98985         labelAlign: 'left',
98986         labelWidth: 100
98987     },
98988     items: [{
98989         xtype: 'fieldset',
98990         defaults: {
98991             labelAlign: 'top'
98992         },
98993         items: [{
98994             name: 'field1'
98995         }, {
98996             name: 'field2'
98997         }]
98998     }, {
98999         xtype: 'fieldset',
99000         items: [{
99001             name: 'field3',
99002             labelWidth: 150
99003         }, {
99004             name: 'field4'
99005         }]
99006     }]
99007 });</code></pre>
99008      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
99009      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
99010      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
99011      */
99012
99013
99014     /**
99015      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
99016      * of any components importing this mixin.
99017      */
99018     initFieldAncestor: function() {
99019         var me = this,
99020             onSubtreeChange = me.onFieldAncestorSubtreeChange;
99021
99022         me.addEvents(
99023             /**
99024              * @event fieldvaliditychange
99025              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
99026              * container changes.
99027              * @param {Ext.form.FieldAncestor} this
99028              * @param {Ext.form.Labelable} The Field instance whose validity changed
99029              * @param {String} isValid The field's new validity state
99030              */
99031             'fieldvaliditychange',
99032
99033             /**
99034              * @event fielderrorchange
99035              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
99036              * instances within this container.
99037              * @param {Ext.form.FieldAncestor} this
99038              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
99039              * @param {String} error The active error message
99040              */
99041             'fielderrorchange'
99042         );
99043
99044         // Catch addition and removal of descendant fields
99045         me.on('add', onSubtreeChange, me);
99046         me.on('remove', onSubtreeChange, me);
99047
99048         me.initFieldDefaults();
99049     },
99050
99051     /**
99052      * @private Initialize the {@link #fieldDefaults} object
99053      */
99054     initFieldDefaults: function() {
99055         if (!this.fieldDefaults) {
99056             this.fieldDefaults = {};
99057         }
99058     },
99059
99060     /**
99061      * @private
99062      * Handle the addition and removal of components in the FieldAncestor component's child tree.
99063      */
99064     onFieldAncestorSubtreeChange: function(parent, child) {
99065         var me = this,
99066             isAdding = !!child.ownerCt;
99067
99068         function handleCmp(cmp) {
99069             var isLabelable = cmp.isFieldLabelable,
99070                 isField = cmp.isFormField;
99071             if (isLabelable || isField) {
99072                 if (isLabelable) {
99073                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
99074                 }
99075                 if (isField) {
99076                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
99077                 }
99078             }
99079             else if (cmp.isContainer) {
99080                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
99081             }
99082         }
99083         handleCmp(child);
99084     },
99085
99086     /**
99087      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99088      * @param {Ext.form.Labelable} labelable The instance that was added
99089      */
99090     onLabelableAdded: function(labelable) {
99091         var me = this;
99092
99093         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
99094         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
99095
99096         labelable.setFieldDefaults(me.fieldDefaults);
99097     },
99098
99099     /**
99100      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
99101      * @param {Ext.form.field.Field} field The field which was added
99102      */
99103     onFieldAdded: function(field) {
99104         var me = this;
99105         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
99106     },
99107
99108     /**
99109      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99110      * @param {Ext.form.Labelable} labelable The instance that was removed
99111      */
99112     onLabelableRemoved: function(labelable) {
99113         var me = this;
99114         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
99115     },
99116
99117     /**
99118      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
99119      * @param {Ext.form.field.Field} field The field which was removed
99120      */
99121     onFieldRemoved: function(field) {
99122         var me = this;
99123         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
99124     },
99125
99126     /**
99127      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
99128      */
99129     handleFieldValidityChange: function(field, isValid) {
99130         var me = this;
99131         me.fireEvent('fieldvaliditychange', me, field, isValid);
99132         me.onFieldValidityChange();
99133     },
99134
99135     /**
99136      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
99137      */
99138     handleFieldErrorChange: function(labelable, activeError) {
99139         var me = this;
99140         me.fireEvent('fielderrorchange', me, labelable, activeError);
99141         me.onFieldErrorChange();
99142     },
99143
99144     /**
99145      * @protected Fired when the validity of any field within the container changes.
99146      * @param {Ext.form.field.Field} The sub-field whose validity changed
99147      * @param {String} The new validity state
99148      */
99149     onFieldValidityChange: Ext.emptyFn,
99150
99151     /**
99152      * @protected Fired when the error message of any field within the container changes.
99153      * @param {Ext.form.Labelable} The sub-field whose active error changed
99154      * @param {String} The new active error message
99155      */
99156     onFieldErrorChange: Ext.emptyFn
99157
99158 });
99159 /**
99160  * @class Ext.layout.container.CheckboxGroup
99161  * @extends Ext.layout.container.Container
99162  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
99163  * It groups the component's sub-items into columns based on the component's
99164  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
99165  *
99166  */
99167 Ext.define('Ext.layout.container.CheckboxGroup', {
99168     extend: 'Ext.layout.container.Container',
99169     alias: ['layout.checkboxgroup'],
99170
99171
99172     onLayout: function() {
99173         var numCols = this.getColCount(),
99174             shadowCt = this.getShadowCt(),
99175             owner = this.owner,
99176             items = owner.items,
99177             shadowItems = shadowCt.items,
99178             numItems = items.length,
99179             colIndex = 0,
99180             i, numRows;
99181
99182         // Distribute the items into the appropriate column containers. We add directly to the
99183         // containers' items collection rather than calling container.add(), because we need the
99184         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
99185         // in case items have been added, removed, or reordered.
99186
99187         shadowItems.each(function(col) {
99188             col.items.clear();
99189         });
99190
99191         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
99192         // from the CheckboxGroup; adjust to match.
99193         while (shadowItems.length > numCols) {
99194             shadowCt.remove(shadowItems.last());
99195         }
99196         while (shadowItems.length < numCols) {
99197             shadowCt.add({
99198                 xtype: 'container',
99199                 cls: owner.groupCls,
99200                 flex: 1
99201             });
99202         }
99203
99204         if (owner.vertical) {
99205             numRows = Math.ceil(numItems / numCols);
99206             for (i = 0; i < numItems; i++) {
99207                 if (i > 0 && i % numRows === 0) {
99208                     colIndex++;
99209                 }
99210                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99211             }
99212         } else {
99213             for (i = 0; i < numItems; i++) {
99214                 colIndex = i % numCols;
99215                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99216             }
99217         }
99218
99219         if (!shadowCt.rendered) {
99220             shadowCt.render(this.getRenderTarget());
99221         } else {
99222             // Ensure all items are rendered in the correct place in the correct column - this won't
99223             // get done by the column containers themselves if their dimensions are not changing.
99224             shadowItems.each(function(col) {
99225                 var layout = col.getLayout();
99226                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
99227             });
99228         }
99229
99230         shadowCt.doComponentLayout();
99231     },
99232
99233
99234     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
99235     renderItems: Ext.emptyFn,
99236
99237
99238     /**
99239      * @private
99240      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
99241      * into columns.
99242      */
99243     getShadowCt: function() {
99244         var me = this,
99245             shadowCt = me.shadowCt,
99246             owner, items, item, columns, columnsIsArray, numCols, i;
99247
99248         if (!shadowCt) {
99249             // Create the column containers based on the owner's 'columns' config
99250             owner = me.owner;
99251             columns = owner.columns;
99252             columnsIsArray = Ext.isArray(columns);
99253             numCols = me.getColCount();
99254             items = [];
99255             for(i = 0; i < numCols; i++) {
99256                 item = {
99257                     xtype: 'container',
99258                     cls: owner.groupCls
99259                 };
99260                 if (columnsIsArray) {
99261                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
99262                     // numbers, used as relative flex values.
99263                     if (columns[i] < 1) {
99264                         item.flex = columns[i];
99265                     } else {
99266                         item.width = columns[i];
99267                     }
99268                 }
99269                 else {
99270                     // All columns the same width
99271                     item.flex = 1;
99272                 }
99273                 items.push(item);
99274             }
99275
99276             // Create the shadow container; delay rendering until after items are added to the columns
99277             shadowCt = me.shadowCt = Ext.createWidget('container', {
99278                 layout: 'hbox',
99279                 items: items,
99280                 ownerCt: owner
99281             });
99282         }
99283         
99284         return shadowCt;
99285     },
99286
99287
99288     /**
99289      * @private Get the number of columns in the checkbox group
99290      */
99291     getColCount: function() {
99292         var owner = this.owner,
99293             colsCfg = owner.columns;
99294         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
99295     }
99296
99297 });
99298
99299 /**
99300  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
99301  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
99302  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
99303  * This is useful for arranging a group of fields or other components within a single item in a form, so
99304  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
99305  * a single label in a form.
99306  * 
99307  * The container's configured {@link #items} will be layed out within the field body area according to the
99308  * configured {@link #layout} type. The default layout is `'autocontainer'`.
99309  * 
99310  * Like regular fields, FieldContainer can inherit its decoration configuration from the
99311  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
99312  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
99313  * it may itself contain.
99314  * 
99315  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
99316  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
99317  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
99318  *
99319  * # Example
99320  * 
99321  *     @example
99322  *     Ext.create('Ext.form.Panel', {
99323  *         title: 'FieldContainer Example',
99324  *         width: 550,
99325  *         bodyPadding: 10,
99326  * 
99327  *         items: [{
99328  *             xtype: 'fieldcontainer',
99329  *             fieldLabel: 'Last Three Jobs',
99330  *             labelWidth: 100,
99331  * 
99332  *             // The body area will contain three text fields, arranged
99333  *             // horizontally, separated by draggable splitters.
99334  *             layout: 'hbox',
99335  *             items: [{
99336  *                 xtype: 'textfield',
99337  *                 flex: 1
99338  *             }, {
99339  *                 xtype: 'splitter'
99340  *             }, {
99341  *                 xtype: 'textfield',
99342  *                 flex: 1
99343  *             }, {
99344  *                 xtype: 'splitter'
99345  *             }, {
99346  *                 xtype: 'textfield',
99347  *                 flex: 1
99348  *             }]
99349  *         }],
99350  *         renderTo: Ext.getBody()
99351  *     });
99352  * 
99353  * # Usage of fieldDefaults
99354  *
99355  *     @example
99356  *     Ext.create('Ext.form.Panel', {
99357  *         title: 'FieldContainer Example',
99358  *         width: 350,
99359  *         bodyPadding: 10,
99360  * 
99361  *         items: [{
99362  *             xtype: 'fieldcontainer',
99363  *             fieldLabel: 'Your Name',
99364  *             labelWidth: 75,
99365  *             defaultType: 'textfield',
99366  * 
99367  *             // Arrange fields vertically, stretched to full width
99368  *             layout: 'anchor',
99369  *             defaults: {
99370  *                 layout: '100%'
99371  *             },
99372  * 
99373  *             // These config values will be applied to both sub-fields, except
99374  *             // for Last Name which will use its own msgTarget.
99375  *             fieldDefaults: {
99376  *                 msgTarget: 'under',
99377  *                 labelAlign: 'top'
99378  *             },
99379  * 
99380  *             items: [{
99381  *                 fieldLabel: 'First Name',
99382  *                 name: 'firstName'
99383  *             }, {
99384  *                 fieldLabel: 'Last Name',
99385  *                 name: 'lastName',
99386  *                 msgTarget: 'under'
99387  *             }]
99388  *         }],
99389  *         renderTo: Ext.getBody()
99390  *     });
99391  * 
99392  * @docauthor Jason Johnston <jason@sencha.com>
99393  */
99394 Ext.define('Ext.form.FieldContainer', {
99395     extend: 'Ext.container.Container',
99396     mixins: {
99397         labelable: 'Ext.form.Labelable',
99398         fieldAncestor: 'Ext.form.FieldAncestor'
99399     },
99400     alias: 'widget.fieldcontainer',
99401
99402     componentLayout: 'field',
99403
99404     /**
99405      * @cfg {Boolean} combineLabels
99406      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
99407      * generate its label by combining the labels of all the fields it contains. Defaults to false.
99408      */
99409     combineLabels: false,
99410
99411     /**
99412      * @cfg {String} labelConnector
99413      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
99414      * set to true. Defaults to ', '.
99415      */
99416     labelConnector: ', ',
99417
99418     /**
99419      * @cfg {Boolean} combineErrors
99420      * If set to true, the field container will automatically combine and display the validation errors from
99421      * all the fields it contains as a single error on the container, according to the configured
99422      * {@link #msgTarget}. Defaults to false.
99423      */
99424     combineErrors: false,
99425
99426     maskOnDisable: false,
99427
99428     initComponent: function() {
99429         var me = this,
99430             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
99431
99432         // Init mixins
99433         me.initLabelable();
99434         me.initFieldAncestor();
99435
99436         me.callParent();
99437     },
99438
99439     /**
99440      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99441      * @param {Ext.form.Labelable} labelable The instance that was added
99442      */
99443     onLabelableAdded: function(labelable) {
99444         var me = this;
99445         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
99446         me.updateLabel();
99447     },
99448
99449     /**
99450      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99451      * @param {Ext.form.Labelable} labelable The instance that was removed
99452      */
99453     onLabelableRemoved: function(labelable) {
99454         var me = this;
99455         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
99456         me.updateLabel();
99457     },
99458
99459     onRender: function() {
99460         var me = this;
99461
99462         me.onLabelableRender();
99463
99464         me.callParent(arguments);
99465     },
99466
99467     initRenderTpl: function() {
99468         var me = this;
99469         if (!me.hasOwnProperty('renderTpl')) {
99470             me.renderTpl = me.getTpl('labelableRenderTpl');
99471         }
99472         return me.callParent();
99473     },
99474
99475     initRenderData: function() {
99476         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
99477     },
99478
99479     /**
99480      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
99481      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
99482      * this method to provide a custom generated label.
99483      */
99484     getFieldLabel: function() {
99485         var label = this.fieldLabel || '';
99486         if (!label && this.combineLabels) {
99487             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
99488                 return field.getFieldLabel();
99489             }).join(this.labelConnector);
99490         }
99491         return label;
99492     },
99493
99494     /**
99495      * @private Updates the content of the labelEl if it is rendered
99496      */
99497     updateLabel: function() {
99498         var me = this,
99499             label = me.labelEl;
99500         if (label) {
99501             label.update(me.getFieldLabel());
99502         }
99503     },
99504
99505
99506     /**
99507      * @private Fired when the error message of any field within the container changes, and updates the
99508      * combined error message to match.
99509      */
99510     onFieldErrorChange: function(field, activeError) {
99511         if (this.combineErrors) {
99512             var me = this,
99513                 oldError = me.getActiveError(),
99514                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
99515                     return field.hasActiveError();
99516                 }),
99517                 newErrors = me.getCombinedErrors(invalidFields);
99518
99519             if (newErrors) {
99520                 me.setActiveErrors(newErrors);
99521             } else {
99522                 me.unsetActiveError();
99523             }
99524
99525             if (oldError !== me.getActiveError()) {
99526                 me.doComponentLayout();
99527             }
99528         }
99529     },
99530
99531     /**
99532      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
99533      * messages from them. Defaults to prepending each message by the field name and a colon. This
99534      * can be overridden to provide custom combined error message handling, for instance changing
99535      * the format of each message or sorting the array (it is sorted in order of appearance by default).
99536      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
99537      * @return {String[]} The combined list of error messages
99538      */
99539     getCombinedErrors: function(invalidFields) {
99540         var forEach = Ext.Array.forEach,
99541             errors = [];
99542         forEach(invalidFields, function(field) {
99543             forEach(field.getActiveErrors(), function(error) {
99544                 var label = field.getFieldLabel();
99545                 errors.push((label ? label + ': ' : '') + error);
99546             });
99547         });
99548         return errors;
99549     },
99550
99551     getTargetEl: function() {
99552         return this.bodyEl || this.callParent();
99553     }
99554 });
99555
99556 /**
99557  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
99558  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
99559  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
99560  * and {@link #validate validating} the group of checkboxes as a whole.
99561  *
99562  * # Validation
99563  *
99564  * Individual checkbox fields themselves have no default validation behavior, but
99565  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
99566  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
99567  * least one of the checkboxes, the entire group will be highlighted as invalid and the
99568  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
99569  *
99570  * # Layout
99571  *
99572  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
99573  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
99574  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
99575  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
99576  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
99577  *
99578  *     @example
99579  *     Ext.create('Ext.form.Panel', {
99580  *         title: 'Checkbox Group',
99581  *         width: 300,
99582  *         height: 125,
99583  *         bodyPadding: 10,
99584  *         renderTo: Ext.getBody(),
99585  *         items:[{
99586  *             xtype: 'checkboxgroup',
99587  *             fieldLabel: 'Two Columns',
99588  *             // Arrange radio buttons into two columns, distributed vertically
99589  *             columns: 2,
99590  *             vertical: true,
99591  *             items: [
99592  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
99593  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
99594  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
99595  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
99596  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
99597  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
99598  *             ]
99599  *         }]
99600  *     });
99601  */
99602 Ext.define('Ext.form.CheckboxGroup', {
99603     extend:'Ext.form.FieldContainer',
99604     mixins: {
99605         field: 'Ext.form.field.Field'
99606     },
99607     alias: 'widget.checkboxgroup',
99608     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
99609
99610     /**
99611      * @cfg {String} name
99612      * @hide
99613      */
99614
99615     /**
99616      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
99617      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
99618      */
99619
99620     /**
99621      * @cfg {String/Number/Number[]} columns
99622      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
99623      * This config can take several types of values:
99624      *
99625      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
99626      *   distributed based on the width of the overall field container. This is the default.
99627      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
99628      *   will be automatically distributed based on the value of {@link #vertical}.
99629      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
99630      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
99631      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
99632      *   (100%) although if you want the controls to take up the entire field container you should do so.
99633      */
99634     columns : 'auto',
99635
99636     /**
99637      * @cfg {Boolean} vertical
99638      * True to distribute contained controls across columns, completely filling each column top to bottom before
99639      * starting on the next column. The number of controls in each column will be automatically calculated to keep
99640      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
99641      * completely filling each row left to right before starting on the next row.
99642      */
99643     vertical : false,
99644
99645     /**
99646      * @cfg {Boolean} allowBlank
99647      * False to validate that at least one item in the group is checked. If no items are selected at
99648      * validation time, {@link #blankText} will be used as the error text.
99649      */
99650     allowBlank : true,
99651
99652     /**
99653      * @cfg {String} blankText
99654      * Error text to display if the {@link #allowBlank} validation fails
99655      */
99656     blankText : "You must select at least one item in this group",
99657
99658     // private
99659     defaultType : 'checkboxfield',
99660
99661     // private
99662     groupCls : Ext.baseCSSPrefix + 'form-check-group',
99663
99664     /**
99665      * @cfg {String} fieldBodyCls
99666      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
99667      * Defaults to 'x-form-checkboxgroup-body'.
99668      */
99669     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
99670
99671     // private
99672     layout: 'checkboxgroup',
99673
99674     initComponent: function() {
99675         var me = this;
99676         me.callParent();
99677         me.initField();
99678     },
99679
99680     /**
99681      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
99682      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
99683      * after they have been initialized.
99684      * @protected
99685      */
99686     initValue: function() {
99687         var me = this,
99688             valueCfg = me.value;
99689         me.originalValue = me.lastValue = valueCfg || me.getValue();
99690         if (valueCfg) {
99691             me.setValue(valueCfg);
99692         }
99693     },
99694
99695     /**
99696      * When a checkbox is added to the group, monitor it for changes
99697      * @param {Object} field
99698      * @protected
99699      */
99700     onFieldAdded: function(field) {
99701         var me = this;
99702         if (field.isCheckbox) {
99703             me.mon(field, 'change', me.checkChange, me);
99704         }
99705         me.callParent(arguments);
99706     },
99707
99708     onFieldRemoved: function(field) {
99709         var me = this;
99710         if (field.isCheckbox) {
99711             me.mun(field, 'change', me.checkChange, me);
99712         }
99713         me.callParent(arguments);
99714     },
99715
99716     // private override - the group value is a complex object, compare using object serialization
99717     isEqual: function(value1, value2) {
99718         var toQueryString = Ext.Object.toQueryString;
99719         return toQueryString(value1) === toQueryString(value2);
99720     },
99721
99722     /**
99723      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
99724      * is set to true and no items are checked.
99725      * @return {String[]} Array of all validation errors
99726      */
99727     getErrors: function() {
99728         var errors = [];
99729         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
99730             errors.push(this.blankText);
99731         }
99732         return errors;
99733     },
99734
99735     /**
99736      * @private Returns all checkbox components within the container
99737      */
99738     getBoxes: function() {
99739         return this.query('[isCheckbox]');
99740     },
99741
99742     /**
99743      * @private Convenience function which calls the given function for every checkbox in the group
99744      * @param {Function} fn The function to call
99745      * @param {Object} scope (Optional) scope object
99746      */
99747     eachBox: function(fn, scope) {
99748         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
99749     },
99750
99751     /**
99752      * Returns an Array of all checkboxes in the container which are currently checked
99753      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
99754      */
99755     getChecked: function() {
99756         return Ext.Array.filter(this.getBoxes(), function(cb) {
99757             return cb.getValue();
99758         });
99759     },
99760
99761     // private override
99762     isDirty: function(){
99763         return Ext.Array.some(this.getBoxes(), function(cb) {
99764             return cb.isDirty();
99765         });
99766     },
99767
99768     // private override
99769     setReadOnly: function(readOnly) {
99770         this.eachBox(function(cb) {
99771             cb.setReadOnly(readOnly);
99772         });
99773         this.readOnly = readOnly;
99774     },
99775
99776     /**
99777      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
99778      * loaded values and clears any validation messages.
99779      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
99780      */
99781     reset: function() {
99782         var me = this,
99783             hadError = me.hasActiveError(),
99784             preventMark = me.preventMark;
99785         me.preventMark = true;
99786         me.batchChanges(function() {
99787             me.eachBox(function(cb) {
99788                 cb.reset();
99789             });
99790         });
99791         me.preventMark = preventMark;
99792         me.unsetActiveError();
99793         if (hadError) {
99794             me.doComponentLayout();
99795         }
99796     },
99797
99798     // private override
99799     resetOriginalValue: function() {
99800         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
99801         // the correct data from getValue()
99802         Ext.defer(function() {
99803             this.callParent();
99804         }, 1, this);
99805     },
99806
99807
99808     /**
99809      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
99810      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
99811      *
99812      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
99813      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
99814      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
99815      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
99816      *     checked and others will be unchecked.
99817      *
99818      * If a checkbox's name is not in the mapping at all, it will be unchecked.
99819      *
99820      * An example:
99821      *
99822      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
99823      *         columns: 3,
99824      *         items: [{
99825      *             name: 'cb1',
99826      *             boxLabel: 'Single 1'
99827      *         }, {
99828      *             name: 'cb2',
99829      *             boxLabel: 'Single 2'
99830      *         }, {
99831      *             name: 'cb3',
99832      *             boxLabel: 'Single 3'
99833      *         }, {
99834      *             name: 'cbGroup',
99835      *             boxLabel: 'Grouped 1'
99836      *             inputValue: 'value1'
99837      *         }, {
99838      *             name: 'cbGroup',
99839      *             boxLabel: 'Grouped 2'
99840      *             inputValue: 'value2'
99841      *         }, {
99842      *             name: 'cbGroup',
99843      *             boxLabel: 'Grouped 3'
99844      *             inputValue: 'value3'
99845      *         }]
99846      *     });
99847      *
99848      *     myCheckboxGroup.setValue({
99849      *         cb1: true,
99850      *         cb3: false,
99851      *         cbGroup: ['value1', 'value3']
99852      *     });
99853      *
99854      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
99855      * 'cbGroup'. The other three checkboxes will be unchecked.
99856      *
99857      * @param {Object} value The mapping of checkbox names to values.
99858      * @return {Ext.form.CheckboxGroup} this
99859      */
99860     setValue: function(value) {
99861         var me = this;
99862         me.batchChanges(function() {
99863             me.eachBox(function(cb) {
99864                 var name = cb.getName(),
99865                     cbValue = false;
99866                 if (value && name in value) {
99867                     if (Ext.isArray(value[name])) {
99868                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
99869                     } else {
99870                         // single value, let the checkbox's own setValue handle conversion
99871                         cbValue = value[name];
99872                     }
99873                 }
99874                 cb.setValue(cbValue);
99875             });
99876         });
99877         return me;
99878     },
99879
99880
99881     /**
99882      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
99883      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
99884      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
99885      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
99886      * will be an Array of the selected inputValues.
99887      *
99888      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
99889      *
99890      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
99891      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
99892      * Ext 4+, use the {@link #getChecked} method instead.
99893      */
99894     getValue: function() {
99895         var values = {};
99896         this.eachBox(function(cb) {
99897             var name = cb.getName(),
99898                 inputValue = cb.inputValue,
99899                 bucket;
99900             if (cb.getValue()) {
99901                 if (name in values) {
99902                     bucket = values[name];
99903                     if (!Ext.isArray(bucket)) {
99904                         bucket = values[name] = [bucket];
99905                     }
99906                     bucket.push(inputValue);
99907                 } else {
99908                     values[name] = inputValue;
99909                 }
99910             }
99911         });
99912         return values;
99913     },
99914
99915     /*
99916      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
99917      */
99918     getSubmitData: function() {
99919         return null;
99920     },
99921
99922     /*
99923      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
99924      */
99925     getModelData: function() {
99926         return null;
99927     },
99928
99929     validate: function() {
99930         var me = this,
99931             errors = me.getErrors(),
99932             isValid = Ext.isEmpty(errors),
99933             wasValid = !me.hasActiveError();
99934
99935         if (isValid) {
99936             me.unsetActiveError();
99937         } else {
99938             me.setActiveError(errors);
99939         }
99940         if (isValid !== wasValid) {
99941             me.fireEvent('validitychange', me, isValid);
99942             me.doComponentLayout();
99943         }
99944
99945         return isValid;
99946     }
99947
99948 }, function() {
99949
99950     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
99951
99952 });
99953
99954
99955 /**
99956  * @private
99957  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
99958  */
99959 Ext.define('Ext.form.CheckboxManager', {
99960     extend: 'Ext.util.MixedCollection',
99961     singleton: true,
99962
99963     getByName: function(name) {
99964         return this.filterBy(function(item) {
99965             return item.name == name;
99966         });
99967     },
99968
99969     getWithValue: function(name, value) {
99970         return this.filterBy(function(item) {
99971             return item.name == name && item.inputValue == value;
99972         });
99973     },
99974
99975     getChecked: function(name) {
99976         return this.filterBy(function(item) {
99977             return item.name == name && item.checked;
99978         });
99979     }
99980 });
99981
99982 /**
99983  * @docauthor Jason Johnston <jason@sencha.com>
99984  *
99985  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
99986  * config will be rendered as the fieldset's `legend`.
99987  *
99988  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
99989  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
99990  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
99991  * layout type.
99992  *
99993  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
99994  *
99995  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
99996  *    the {@link #title legend title}, or:
99997  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
99998  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
99999  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
100000  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
100001  *
100002  * # Example usage
100003  *
100004  *     @example
100005  *     Ext.create('Ext.form.Panel', {
100006  *         title: 'Simple Form with FieldSets',
100007  *         labelWidth: 75, // label settings here cascade unless overridden
100008  *         url: 'save-form.php',
100009  *         frame: true,
100010  *         bodyStyle: 'padding:5px 5px 0',
100011  *         width: 550,
100012  *         renderTo: Ext.getBody(),
100013  *         layout: 'column', // arrange fieldsets side by side
100014  *         defaults: {
100015  *             bodyPadding: 4
100016  *         },
100017  *         items: [{
100018  *             // Fieldset in Column 1 - collapsible via toggle button
100019  *             xtype:'fieldset',
100020  *             columnWidth: 0.5,
100021  *             title: 'Fieldset 1',
100022  *             collapsible: true,
100023  *             defaultType: 'textfield',
100024  *             defaults: {anchor: '100%'},
100025  *             layout: 'anchor',
100026  *             items :[{
100027  *                 fieldLabel: 'Field 1',
100028  *                 name: 'field1'
100029  *             }, {
100030  *                 fieldLabel: 'Field 2',
100031  *                 name: 'field2'
100032  *             }]
100033  *         }, {
100034  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
100035  *             xtype:'fieldset',
100036  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
100037  *             columnWidth: 0.5,
100038  *             checkboxToggle: true,
100039  *             collapsed: true, // fieldset initially collapsed
100040  *             layout:'anchor',
100041  *             items :[{
100042  *                 xtype: 'panel',
100043  *                 anchor: '100%',
100044  *                 title: 'Panel inside a fieldset',
100045  *                 frame: true,
100046  *                 height: 52
100047  *             }]
100048  *         }]
100049  *     });
100050  */
100051 Ext.define('Ext.form.FieldSet', {
100052     extend: 'Ext.container.Container',
100053     alias: 'widget.fieldset',
100054     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
100055
100056     /**
100057      * @cfg {String} title
100058      * A title to be displayed in the fieldset's legend. May contain HTML markup.
100059      */
100060
100061     /**
100062      * @cfg {Boolean} [checkboxToggle=false]
100063      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
100064      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
100065      * the {@link #checkboxName}.
100066      */
100067
100068     /**
100069      * @cfg {String} checkboxName
100070      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
100071      * (defaults to '[fieldset id]-checkbox').
100072      */
100073
100074     /**
100075      * @cfg {Boolean} [collapsible=false]
100076      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
100077      * into the legend element, false to keep the fieldset statically sized with no collapse button.
100078      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
100079      * fieldset by default.
100080      */
100081
100082     /**
100083      * @cfg {Boolean} collapsed
100084      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
100085      * will also be unchecked by default.
100086      */
100087     collapsed: false,
100088
100089     /**
100090      * @property {Ext.Component} legend
100091      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
100092      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
100093      */
100094
100095     /**
100096      * @cfg {String} [baseCls='x-fieldset']
100097      * The base CSS class applied to the fieldset.
100098      */
100099     baseCls: Ext.baseCSSPrefix + 'fieldset',
100100
100101     /**
100102      * @cfg {String} layout
100103      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
100104      */
100105     layout: 'anchor',
100106
100107     componentLayout: 'fieldset',
100108
100109     // No aria role necessary as fieldset has its own recognized semantics
100110     ariaRole: '',
100111
100112     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
100113
100114     maskOnDisable: false,
100115
100116     getElConfig: function(){
100117         return {tag: 'fieldset', id: this.id};
100118     },
100119
100120     initComponent: function() {
100121         var me = this,
100122             baseCls = me.baseCls;
100123
100124         me.callParent();
100125
100126         // Create the Legend component if needed
100127         me.initLegend();
100128
100129         // Add body el
100130         me.addChildEls('body');
100131
100132         if (me.collapsed) {
100133             me.addCls(baseCls + '-collapsed');
100134             me.collapse();
100135         }
100136     },
100137
100138     // private
100139     onRender: function(container, position) {
100140         this.callParent(arguments);
100141         // Make sure the legend is created and rendered
100142         this.initLegend();
100143     },
100144
100145     /**
100146      * @private
100147      * Initialize and render the legend component if necessary
100148      */
100149     initLegend: function() {
100150         var me = this,
100151             legendItems,
100152             legend = me.legend;
100153
100154         // Create the legend component if needed and it hasn't been already
100155         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
100156             legendItems = [];
100157
100158             // Checkbox
100159             if (me.checkboxToggle) {
100160                 legendItems.push(me.createCheckboxCmp());
100161             }
100162             // Toggle button
100163             else if (me.collapsible) {
100164                 legendItems.push(me.createToggleCmp());
100165             }
100166
100167             // Title
100168             legendItems.push(me.createTitleCmp());
100169
100170             legend = me.legend = Ext.create('Ext.container.Container', {
100171                 baseCls: me.baseCls + '-header',
100172                 ariaRole: '',
100173                 ownerCt: this,
100174                 getElConfig: function(){
100175                     var result = {
100176                         tag: 'legend',
100177                         cls: this.baseCls
100178                     };
100179
100180                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
100181                     // So here we change every <div> into <span>s. Therefore the following
100182                     // clearer is not needed and since div introduces a lot of subsequent
100183                     // problems, it is actually harmful.
100184                     if (!Ext.isGecko3) {
100185                         result.children = [{
100186                             cls: Ext.baseCSSPrefix + 'clear'
100187                         }];
100188                     }
100189                     return result;
100190                 },
100191                 items: legendItems
100192             });
100193         }
100194
100195         // Make sure legend is rendered if the fieldset is rendered
100196         if (legend && !legend.rendered && me.rendered) {
100197             me.legend.render(me.el, me.body); //insert before body element
100198         }
100199     },
100200
100201     /**
100202      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
100203      * customize the title component.
100204      * @return Ext.Component
100205      * @protected
100206      */
100207     createTitleCmp: function() {
100208         var me = this;
100209         me.titleCmp = Ext.create('Ext.Component', {
100210             html: me.title,
100211             getElConfig: function() {
100212                 return {
100213                     tag: Ext.isGecko3 ? 'span' : 'div',
100214                     cls: me.titleCmp.cls,
100215                     id: me.titleCmp.id
100216                 };
100217             },
100218             cls: me.baseCls + '-header-text'
100219         });
100220         return me.titleCmp;
100221     },
100222
100223     /**
100224      * @property {Ext.form.field.Checkbox} checkboxCmp
100225      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
100226      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
100227      */
100228
100229     /**
100230      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
100231      * customize the checkbox's configuration or even return an entirely different component type.
100232      * @return Ext.Component
100233      * @protected
100234      */
100235     createCheckboxCmp: function() {
100236         var me = this,
100237             suffix = '-checkbox';
100238
100239         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
100240             getElConfig: function() {
100241                 return {
100242                     tag: Ext.isGecko3 ? 'span' : 'div',
100243                     id: me.checkboxCmp.id,
100244                     cls: me.checkboxCmp.cls
100245                 };
100246             },
100247             name: me.checkboxName || me.id + suffix,
100248             cls: me.baseCls + '-header' + suffix,
100249             checked: !me.collapsed,
100250             listeners: {
100251                 change: me.onCheckChange,
100252                 scope: me
100253             }
100254         });
100255         return me.checkboxCmp;
100256     },
100257
100258     /**
100259      * @property {Ext.panel.Tool} toggleCmp
100260      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
100261      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
100262      */
100263
100264     /**
100265      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
100266      * customize the toggle component.
100267      * @return Ext.Component
100268      * @protected
100269      */
100270     createToggleCmp: function() {
100271         var me = this;
100272         me.toggleCmp = Ext.create('Ext.panel.Tool', {
100273             getElConfig: function() {
100274                 return {
100275                     tag: Ext.isGecko3 ? 'span' : 'div',
100276                     id: me.toggleCmp.id,
100277                     cls: me.toggleCmp.cls
100278                 };
100279             },
100280             type: 'toggle',
100281             handler: me.toggle,
100282             scope: me
100283         });
100284         return me.toggleCmp;
100285     },
100286
100287     /**
100288      * Sets the title of this fieldset
100289      * @param {String} title The new title
100290      * @return {Ext.form.FieldSet} this
100291      */
100292     setTitle: function(title) {
100293         var me = this;
100294         me.title = title;
100295         me.initLegend();
100296         me.titleCmp.update(title);
100297         return me;
100298     },
100299
100300     getTargetEl : function() {
100301         return this.body || this.frameBody || this.el;
100302     },
100303
100304     getContentTarget: function() {
100305         return this.body;
100306     },
100307
100308     /**
100309      * @private
100310      * Include the legend component in the items for ComponentQuery
100311      */
100312     getRefItems: function(deep) {
100313         var refItems = this.callParent(arguments),
100314             legend = this.legend;
100315
100316         // Prepend legend items to ensure correct order
100317         if (legend) {
100318             refItems.unshift(legend);
100319             if (deep) {
100320                 refItems.unshift.apply(refItems, legend.getRefItems(true));
100321             }
100322         }
100323         return refItems;
100324     },
100325
100326     /**
100327      * Expands the fieldset.
100328      * @return {Ext.form.FieldSet} this
100329      */
100330     expand : function(){
100331         return this.setExpanded(true);
100332     },
100333
100334     /**
100335      * Collapses the fieldset.
100336      * @return {Ext.form.FieldSet} this
100337      */
100338     collapse : function() {
100339         return this.setExpanded(false);
100340     },
100341
100342     /**
100343      * @private Collapse or expand the fieldset
100344      */
100345     setExpanded: function(expanded) {
100346         var me = this,
100347             checkboxCmp = me.checkboxCmp;
100348
100349         expanded = !!expanded;
100350
100351         if (checkboxCmp) {
100352             checkboxCmp.setValue(expanded);
100353         }
100354
100355         if (expanded) {
100356             me.removeCls(me.baseCls + '-collapsed');
100357         } else {
100358             me.addCls(me.baseCls + '-collapsed');
100359         }
100360         me.collapsed = !expanded;
100361         if (expanded) {
100362             // ensure subitems will get rendered and layed out when expanding
100363             me.getComponentLayout().childrenChanged = true;
100364         }
100365         me.doComponentLayout();
100366         return me;
100367     },
100368
100369     /**
100370      * Toggle the fieldset's collapsed state to the opposite of what it is currently
100371      */
100372     toggle: function() {
100373         this.setExpanded(!!this.collapsed);
100374     },
100375
100376     /**
100377      * @private
100378      * Handle changes in the checkbox checked state
100379      */
100380     onCheckChange: function(cmp, checked) {
100381         this.setExpanded(checked);
100382     },
100383
100384     beforeDestroy : function() {
100385         var legend = this.legend;
100386         if (legend) {
100387             legend.destroy();
100388         }
100389         this.callParent();
100390     }
100391 });
100392
100393 /**
100394  * @docauthor Jason Johnston <jason@sencha.com>
100395  *
100396  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
100397  * in that form using the {@link #forId} property.
100398  * 
100399  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
100400  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
100401  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
100402  * Ext.form.Label should only be used when your layout can not be achieved with the standard
100403  * {@link Ext.form.Labelable field layout}.
100404  * 
100405  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
100406  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
100407  * of that field.
100408  * 
100409  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
100410  * difference between the two is that the former will automatically escape HTML characters when rendering, while
100411  * the latter will not.
100412  *
100413  * # Example
100414  * 
100415  * This example creates a Label after its associated Text field, an arrangement that cannot currently
100416  * be achieved using the standard Field layout's labelAlign.
100417  * 
100418  *     @example
100419  *     Ext.create('Ext.form.Panel', {
100420  *         title: 'Field with Label',
100421  *         width: 400,
100422  *         bodyPadding: 10,
100423  *         renderTo: Ext.getBody(),
100424  *         layout: {
100425  *             type: 'hbox',
100426  *             align: 'middle'
100427  *         },
100428  *         items: [{
100429  *             xtype: 'textfield',
100430  *             hideLabel: true,
100431  *             flex: 1
100432  *         }, {
100433  *             xtype: 'label',
100434  *             forId: 'myFieldId',
100435  *             text: 'My Awesome Field',
100436  *             margins: '0 0 0 10'
100437  *         }]
100438  *     });
100439  */
100440 Ext.define('Ext.form.Label', {
100441     extend:'Ext.Component',
100442     alias: 'widget.label',
100443     requires: ['Ext.util.Format'],
100444
100445     /**
100446      * @cfg {String} [text='']
100447      * The plain text to display within the label. If you need to include HTML
100448      * tags within the label's innerHTML, use the {@link #html} config instead.
100449      */
100450     /**
100451      * @cfg {String} forId
100452      * The id of the input element to which this label will be bound via the standard HTML 'for'
100453      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
100454      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
100455      * the {@link Ext.form.field.Base#inputId inputId} of that field.
100456      */
100457     /**
100458      * @cfg {String} [html='']
100459      * An HTML fragment that will be used as the label's innerHTML.
100460      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
100461      */
100462     
100463     maskOnDisable: false,
100464     getElConfig: function(){
100465         var me = this;
100466         return {
100467             tag: 'label', 
100468             id: me.id, 
100469             htmlFor: me.forId || '',
100470             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
100471         };
100472     },
100473
100474     /**
100475      * Updates the label's innerHTML with the specified string.
100476      * @param {String} text The new label text
100477      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
100478      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
100479      * than rendering them as string literals per the default logic.
100480      * @return {Ext.form.Label} this
100481      */
100482     setText : function(text, encode){
100483         var me = this;
100484         
100485         encode = encode !== false;
100486         if(encode) {
100487             me.text = text;
100488             delete me.html;
100489         } else {
100490             me.html = text;
100491             delete me.text;
100492         }
100493         
100494         if(me.rendered){
100495             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
100496         }
100497         return this;
100498     }
100499 });
100500
100501
100502 /**
100503  * @docauthor Jason Johnston <jason@sencha.com>
100504  * 
100505  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
100506  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
100507  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
100508  * working with the BasicForm and the collection of Fields.
100509  * 
100510  * # Layout
100511  * 
100512  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
100513  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
100514  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
100515  * 
100516  * # BasicForm
100517  * 
100518  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
100519  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
100520  * the internal BasicForm when it is created.
100521  * 
100522  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
100523  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
100524  * configuration settings to `this` will *not* affect the BasicForm's configuration.
100525  * 
100526  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
100527  * listened for on the FormPanel itself:
100528  * 
100529  * - {@link Ext.form.Basic#beforeaction beforeaction}
100530  * - {@link Ext.form.Basic#actionfailed actionfailed}
100531  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
100532  * - {@link Ext.form.Basic#validitychange validitychange}
100533  * - {@link Ext.form.Basic#dirtychange dirtychange}
100534  * 
100535  * # Field Defaults
100536  * 
100537  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
100538  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
100539  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
100540  * for details of how the defaults are applied.
100541  * 
100542  * # Form Validation
100543  * 
100544  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
100545  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
100546  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
100547  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
100548  * 
100549  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
100550  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
100551  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
100552  * any component type.
100553  * 
100554  * For more information on form validation see the following:
100555  * 
100556  * - {@link Ext.form.field.Field#validateOnChange}
100557  * - {@link #pollForChanges} and {@link #pollInterval}
100558  * - {@link Ext.form.field.VTypes}
100559  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
100560  * 
100561  * # Form Submission
100562  * 
100563  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
100564  * {@link Ext.form.Basic} for details.
100565  *
100566  * # Example usage
100567  * 
100568  *     @example
100569  *     Ext.create('Ext.form.Panel', {
100570  *         title: 'Simple Form',
100571  *         bodyPadding: 5,
100572  *         width: 350,
100573  * 
100574  *         // The form will submit an AJAX request to this URL when submitted
100575  *         url: 'save-form.php',
100576  * 
100577  *         // Fields will be arranged vertically, stretched to full width
100578  *         layout: 'anchor',
100579  *         defaults: {
100580  *             anchor: '100%'
100581  *         },
100582  * 
100583  *         // The fields
100584  *         defaultType: 'textfield',
100585  *         items: [{
100586  *             fieldLabel: 'First Name',
100587  *             name: 'first',
100588  *             allowBlank: false
100589  *         },{
100590  *             fieldLabel: 'Last Name',
100591  *             name: 'last',
100592  *             allowBlank: false
100593  *         }],
100594  * 
100595  *         // Reset and Submit buttons
100596  *         buttons: [{
100597  *             text: 'Reset',
100598  *             handler: function() {
100599  *                 this.up('form').getForm().reset();
100600  *             }
100601  *         }, {
100602  *             text: 'Submit',
100603  *             formBind: true, //only enabled once the form is valid
100604  *             disabled: true,
100605  *             handler: function() {
100606  *                 var form = this.up('form').getForm();
100607  *                 if (form.isValid()) {
100608  *                     form.submit({
100609  *                         success: function(form, action) {
100610  *                            Ext.Msg.alert('Success', action.result.msg);
100611  *                         },
100612  *                         failure: function(form, action) {
100613  *                             Ext.Msg.alert('Failed', action.result.msg);
100614  *                         }
100615  *                     });
100616  *                 }
100617  *             }
100618  *         }],
100619  *         renderTo: Ext.getBody()
100620  *     });
100621  *
100622  */
100623 Ext.define('Ext.form.Panel', {
100624     extend:'Ext.panel.Panel',
100625     mixins: {
100626         fieldAncestor: 'Ext.form.FieldAncestor'
100627     },
100628     alias: 'widget.form',
100629     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
100630     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
100631
100632     /**
100633      * @cfg {Boolean} pollForChanges
100634      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
100635      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
100636      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
100637      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
100638      * do not fire native events. Defaults to `false`.
100639      */
100640
100641     /**
100642      * @cfg {Number} pollInterval
100643      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
100644      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
100645      */
100646
100647     /**
100648      * @cfg {String} layout
100649      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
100650      * Defaults to `'anchor'`.
100651      */
100652     layout: 'anchor',
100653
100654     ariaRole: 'form',
100655
100656     initComponent: function() {
100657         var me = this;
100658
100659         if (me.frame) {
100660             me.border = false;
100661         }
100662
100663         me.initFieldAncestor();
100664         me.callParent();
100665
100666         me.relayEvents(me.form, [
100667             'beforeaction',
100668             'actionfailed',
100669             'actioncomplete',
100670             'validitychange',
100671             'dirtychange'
100672         ]);
100673
100674         // Start polling if configured
100675         if (me.pollForChanges) {
100676             me.startPolling(me.pollInterval || 500);
100677         }
100678     },
100679
100680     initItems: function() {
100681         // Create the BasicForm
100682         var me = this;
100683
100684         me.form = me.createForm();
100685         me.callParent();
100686         me.form.initialize();
100687     },
100688
100689     /**
100690      * @private
100691      */
100692     createForm: function() {
100693         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
100694     },
100695
100696     /**
100697      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
100698      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
100699      */
100700     getForm: function() {
100701         return this.form;
100702     },
100703
100704     /**
100705      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
100706      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
100707      * @param {Ext.data.Model} record The record to load
100708      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
100709      */
100710     loadRecord: function(record) {
100711         return this.getForm().loadRecord(record);
100712     },
100713
100714     /**
100715      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
100716      * @return {Ext.data.Model} The loaded instance
100717      */
100718     getRecord: function() {
100719         return this.getForm().getRecord();
100720     },
100721
100722     /**
100723      * Convenience function for fetching the current value of each field in the form. This is the same as calling
100724      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
100725      * @return {Object} The current form field values, keyed by field name
100726      */
100727     getValues: function() {
100728         return this.getForm().getValues();
100729     },
100730
100731     beforeDestroy: function() {
100732         this.stopPolling();
100733         this.form.destroy();
100734         this.callParent();
100735     },
100736
100737     /**
100738      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
100739      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
100740      * {@link Ext.form.Basic#doAction} for details)
100741      */
100742     load: function(options) {
100743         this.form.load(options);
100744     },
100745
100746     /**
100747      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
100748      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
100749      * {@link Ext.form.Basic#doAction} for details)
100750      */
100751     submit: function(options) {
100752         this.form.submit(options);
100753     },
100754
100755     /*
100756      * Inherit docs, not using onDisable because it only gets fired
100757      * when the component is rendered.
100758      */
100759     disable: function(silent) {
100760         this.callParent(arguments);
100761         this.form.getFields().each(function(field) {
100762             field.disable();
100763         });
100764     },
100765
100766     /*
100767      * Inherit docs, not using onEnable because it only gets fired
100768      * when the component is rendered.
100769      */
100770     enable: function(silent) {
100771         this.callParent(arguments);
100772         this.form.getFields().each(function(field) {
100773             field.enable();
100774         });
100775     },
100776
100777     /**
100778      * Start an interval task to continuously poll all the fields in the form for changes in their
100779      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
100780      * @param {Number} interval The interval in milliseconds at which the check should run.
100781      */
100782     startPolling: function(interval) {
100783         this.stopPolling();
100784         var task = Ext.create('Ext.util.TaskRunner', interval);
100785         task.start({
100786             interval: 0,
100787             run: this.checkChange,
100788             scope: this
100789         });
100790         this.pollTask = task;
100791     },
100792
100793     /**
100794      * Stop a running interval task that was started by {@link #startPolling}.
100795      */
100796     stopPolling: function() {
100797         var task = this.pollTask;
100798         if (task) {
100799             task.stopAll();
100800             delete this.pollTask;
100801         }
100802     },
100803
100804     /**
100805      * Forces each field within the form panel to
100806      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
100807      */
100808     checkChange: function() {
100809         this.form.getFields().each(function(field) {
100810             field.checkChange();
100811         });
100812     }
100813 });
100814
100815 /**
100816  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
100817  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
100818  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
100819  * group of radio buttons as a whole.
100820  *
100821  * # Validation
100822  *
100823  * Individual radio buttons themselves have no default validation behavior, but
100824  * sometimes you want to require a user to select one of a group of radios. RadioGroup
100825  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
100826  * one of the radio buttons, the entire group will be highlighted as invalid and the
100827  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
100828  *
100829  * # Layout
100830  *
100831  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
100832  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
100833  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
100834  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
100835  * the Radio components at any depth will still be managed by the RadioGroup's validation.
100836  *
100837  * # Example usage
100838  *
100839  *     @example
100840  *     Ext.create('Ext.form.Panel', {
100841  *         title: 'RadioGroup Example',
100842  *         width: 300,
100843  *         height: 125,
100844  *         bodyPadding: 10,
100845  *         renderTo: Ext.getBody(),
100846  *         items:[{
100847  *             xtype: 'radiogroup',
100848  *             fieldLabel: 'Two Columns',
100849  *             // Arrange radio buttons into two columns, distributed vertically
100850  *             columns: 2,
100851  *             vertical: true,
100852  *             items: [
100853  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
100854  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
100855  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
100856  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
100857  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
100858  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
100859  *             ]
100860  *         }]
100861  *     });
100862  *
100863  */
100864 Ext.define('Ext.form.RadioGroup', {
100865     extend: 'Ext.form.CheckboxGroup',
100866     alias: 'widget.radiogroup',
100867
100868     /**
100869      * @cfg {Ext.form.field.Radio[]/Object[]} items
100870      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
100871      */
100872     /**
100873      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
100874      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
100875      * be used as the error text.
100876      */
100877     allowBlank : true,
100878     /**
100879      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
100880      */
100881     blankText : 'You must select one item in this group',
100882
100883     // private
100884     defaultType : 'radiofield',
100885
100886     // private
100887     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
100888
100889     getBoxes: function() {
100890         return this.query('[isRadio]');
100891     },
100892
100893     /**
100894      * Sets the value of the radio group. The radio with corresponding name and value will be set.
100895      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
100896      * for each name.
100897      * 
100898      * @param {Object} value The map from names to values to be set.
100899      * @return {Ext.form.CheckboxGroup} this
100900      */
100901     setValue: function(value) {
100902         var me = this;
100903         if (Ext.isObject(value)) {
100904             Ext.Object.each(value, function(name, cbValue) {
100905                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
100906                 radios.each(function(cb) {
100907                     cb.setValue(true);
100908                 });
100909             });
100910         }
100911         return me;
100912     }
100913 });
100914
100915 /**
100916  * @private
100917  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
100918  */
100919 Ext.define('Ext.form.RadioManager', {
100920     extend: 'Ext.util.MixedCollection',
100921     singleton: true,
100922
100923     getByName: function(name) {
100924         return this.filterBy(function(item) {
100925             return item.name == name;
100926         });
100927     },
100928
100929     getWithValue: function(name, value) {
100930         return this.filterBy(function(item) {
100931             return item.name == name && item.inputValue == value;
100932         });
100933     },
100934
100935     getChecked: function(name) {
100936         return this.findBy(function(item) {
100937             return item.name == name && item.checked;
100938         });
100939     }
100940 });
100941
100942 /**
100943  * @class Ext.form.action.DirectLoad
100944  * @extends Ext.form.action.Load
100945  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
100946  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
100947  * <pre><code>
100948 var myFormPanel = new Ext.form.Panel({
100949     // configs for FormPanel
100950     title: 'Basic Information',
100951     renderTo: document.body,
100952     width: 300, height: 160,
100953     padding: 10,
100954
100955     // configs apply to child items
100956     defaults: {anchor: '100%'},
100957     defaultType: 'textfield',
100958     items: [{
100959         fieldLabel: 'Name',
100960         name: 'name'
100961     },{
100962         fieldLabel: 'Email',
100963         name: 'email'
100964     },{
100965         fieldLabel: 'Company',
100966         name: 'company'
100967     }],
100968
100969     // configs for BasicForm
100970     api: {
100971         // The server-side method to call for load() requests
100972         load: Profile.getBasicInfo,
100973         // The server-side must mark the submit handler as a 'formHandler'
100974         submit: Profile.updateBasicInfo
100975     },
100976     // specify the order for the passed params
100977     paramOrder: ['uid', 'foo']
100978 });
100979
100980 // load the form
100981 myFormPanel.getForm().load({
100982     // pass 2 arguments to server side getBasicInfo method (len=2)
100983     params: {
100984         foo: 'bar',
100985         uid: 34
100986     }
100987 });
100988  * </code></pre>
100989  * The data packet sent to the server will resemble something like:
100990  * <pre><code>
100991 [
100992     {
100993         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
100994         "data":[34,"bar"] // note the order of the params
100995     }
100996 ]
100997  * </code></pre>
100998  * The form will process a data packet returned by the server that is similar
100999  * to the following format:
101000  * <pre><code>
101001 [
101002     {
101003         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101004         "result":{
101005             "success":true,
101006             "data":{
101007                 "name":"Fred Flintstone",
101008                 "company":"Slate Rock and Gravel",
101009                 "email":"fred.flintstone@slaterg.com"
101010             }
101011         }
101012     }
101013 ]
101014  * </code></pre>
101015  */
101016 Ext.define('Ext.form.action.DirectLoad', {
101017     extend:'Ext.form.action.Load',
101018     requires: ['Ext.direct.Manager'],
101019     alternateClassName: 'Ext.form.Action.DirectLoad',
101020     alias: 'formaction.directload',
101021
101022     type: 'directload',
101023
101024     run: function() {
101025         this.form.api.load.apply(window, this.getArgs());
101026     },
101027
101028     /**
101029      * @private
101030      * Build the arguments to be sent to the Direct call.
101031      * @return Array
101032      */
101033     getArgs: function() {
101034         var me = this,
101035             args = [],
101036             form = me.form,
101037             paramOrder = form.paramOrder,
101038             params = me.getParams(),
101039             i, len;
101040
101041         // If a paramOrder was specified, add the params into the argument list in that order.
101042         if (paramOrder) {
101043             for (i = 0, len = paramOrder.length; i < len; i++) {
101044                 args.push(params[paramOrder[i]]);
101045             }
101046         }
101047         // If paramsAsHash was specified, add all the params as a single object argument.
101048         else if (form.paramsAsHash) {
101049             args.push(params);
101050         }
101051
101052         // Add the callback and scope to the end of the arguments list
101053         args.push(me.onSuccess, me);
101054
101055         return args;
101056     },
101057
101058     // Direct actions have already been processed and therefore
101059     // we can directly set the result; Direct Actions do not have
101060     // a this.response property.
101061     processResponse: function(result) {
101062         return (this.result = result);
101063     },
101064
101065     onSuccess: function(result, trans) {
101066         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
101067             result = {};
101068         }
101069         this.callParent([result]);
101070     }
101071 });
101072
101073
101074
101075 /**
101076  * @class Ext.form.action.DirectSubmit
101077  * @extends Ext.form.action.Submit
101078  * <p>Provides Ext.direct support for submitting form data.</p>
101079  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
101080  * <pre><code>
101081 var myFormPanel = new Ext.form.Panel({
101082     // configs for FormPanel
101083     title: 'Basic Information',
101084     renderTo: document.body,
101085     width: 300, height: 160,
101086     padding: 10,
101087     buttons:[{
101088         text: 'Submit',
101089         handler: function(){
101090             myFormPanel.getForm().submit({
101091                 params: {
101092                     foo: 'bar',
101093                     uid: 34
101094                 }
101095             });
101096         }
101097     }],
101098
101099     // configs apply to child items
101100     defaults: {anchor: '100%'},
101101     defaultType: 'textfield',
101102     items: [{
101103         fieldLabel: 'Name',
101104         name: 'name'
101105     },{
101106         fieldLabel: 'Email',
101107         name: 'email'
101108     },{
101109         fieldLabel: 'Company',
101110         name: 'company'
101111     }],
101112
101113     // configs for BasicForm
101114     api: {
101115         // The server-side method to call for load() requests
101116         load: Profile.getBasicInfo,
101117         // The server-side must mark the submit handler as a 'formHandler'
101118         submit: Profile.updateBasicInfo
101119     },
101120     // specify the order for the passed params
101121     paramOrder: ['uid', 'foo']
101122 });
101123  * </code></pre>
101124  * The data packet sent to the server will resemble something like:
101125  * <pre><code>
101126 {
101127     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101128     "result":{
101129         "success":true,
101130         "id":{
101131             "extAction":"Profile","extMethod":"updateBasicInfo",
101132             "extType":"rpc","extTID":"6","extUpload":"false",
101133             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
101134         }
101135     }
101136 }
101137  * </code></pre>
101138  * The form will process a data packet returned by the server that is similar
101139  * to the following:
101140  * <pre><code>
101141 // sample success packet (batched requests)
101142 [
101143     {
101144         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
101145         "result":{
101146             "success":true
101147         }
101148     }
101149 ]
101150
101151 // sample failure packet (one request)
101152 {
101153         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101154         "result":{
101155             "errors":{
101156                 "email":"already taken"
101157             },
101158             "success":false,
101159             "foo":"bar"
101160         }
101161 }
101162  * </code></pre>
101163  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
101164  */
101165 Ext.define('Ext.form.action.DirectSubmit', {
101166     extend:'Ext.form.action.Submit',
101167     requires: ['Ext.direct.Manager'],
101168     alternateClassName: 'Ext.form.Action.DirectSubmit',
101169     alias: 'formaction.directsubmit',
101170
101171     type: 'directsubmit',
101172
101173     doSubmit: function() {
101174         var me = this,
101175             callback = Ext.Function.bind(me.onSuccess, me),
101176             formEl = me.buildForm();
101177         me.form.api.submit(formEl, callback, me);
101178         Ext.removeNode(formEl);
101179     },
101180
101181     // Direct actions have already been processed and therefore
101182     // we can directly set the result; Direct Actions do not have
101183     // a this.response property.
101184     processResponse: function(result) {
101185         return (this.result = result);
101186     },
101187
101188     onSuccess: function(response, trans) {
101189         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
101190             response = {};
101191         }
101192         this.callParent([response]);
101193     }
101194 });
101195
101196 /**
101197  * @class Ext.form.action.StandardSubmit
101198  * @extends Ext.form.action.Submit
101199  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
101200  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
101201  * <p>If validation of the form fields fails, the Form's afterAction method
101202  * will be called. Otherwise, afterAction will not be called.</p>
101203  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
101204  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
101205  * config option is <tt>true</tt>.</p>
101206  */
101207 Ext.define('Ext.form.action.StandardSubmit', {
101208     extend:'Ext.form.action.Submit',
101209     alias: 'formaction.standardsubmit',
101210
101211     /**
101212      * @cfg {String} target
101213      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
101214      * the target will be the current window/frame.
101215      */
101216
101217     /**
101218      * @private
101219      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
101220      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
101221      * {@link Ext.form.Basic#baseParams baseParams}.
101222      */
101223     doSubmit: function() {
101224         var form = this.buildForm();
101225         form.submit();
101226         Ext.removeNode(form);
101227     }
101228
101229 });
101230
101231 /**
101232  * @docauthor Robert Dougan <rob@sencha.com>
101233  *
101234  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
101235  * parent class for {@link Ext.form.field.Radio radio buttons}.
101236  *
101237  * # Labeling
101238  *
101239  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
101240  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
101241  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
101242  *
101243  * # Values
101244  *
101245  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
101246  * The following values will check the checkbox:
101247  *
101248  * - `true`
101249  * - `'true'`
101250  * - `'1'`
101251  * - `'on'`
101252  *
101253  * Any other value will uncheck the checkbox.
101254  *
101255  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
101256  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
101257  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
101258  * will be used.
101259  *
101260  * # Example usage
101261  *
101262  *     @example
101263  *     Ext.create('Ext.form.Panel', {
101264  *         bodyPadding: 10,
101265  *         width: 300,
101266  *         title: 'Pizza Order',
101267  *         items: [
101268  *             {
101269  *                 xtype: 'fieldcontainer',
101270  *                 fieldLabel: 'Toppings',
101271  *                 defaultType: 'checkboxfield',
101272  *                 items: [
101273  *                     {
101274  *                         boxLabel  : 'Anchovies',
101275  *                         name      : 'topping',
101276  *                         inputValue: '1',
101277  *                         id        : 'checkbox1'
101278  *                     }, {
101279  *                         boxLabel  : 'Artichoke Hearts',
101280  *                         name      : 'topping',
101281  *                         inputValue: '2',
101282  *                         checked   : true,
101283  *                         id        : 'checkbox2'
101284  *                     }, {
101285  *                         boxLabel  : 'Bacon',
101286  *                         name      : 'topping',
101287  *                         inputValue: '3',
101288  *                         id        : 'checkbox3'
101289  *                     }
101290  *                 ]
101291  *             }
101292  *         ],
101293  *         bbar: [
101294  *             {
101295  *                 text: 'Select Bacon',
101296  *                 handler: function() {
101297  *                     Ext.getCmp('checkbox3').setValue(true);
101298  *                 }
101299  *             },
101300  *             '-',
101301  *             {
101302  *                 text: 'Select All',
101303  *                 handler: function() {
101304  *                     Ext.getCmp('checkbox1').setValue(true);
101305  *                     Ext.getCmp('checkbox2').setValue(true);
101306  *                     Ext.getCmp('checkbox3').setValue(true);
101307  *                 }
101308  *             },
101309  *             {
101310  *                 text: 'Deselect All',
101311  *                 handler: function() {
101312  *                     Ext.getCmp('checkbox1').setValue(false);
101313  *                     Ext.getCmp('checkbox2').setValue(false);
101314  *                     Ext.getCmp('checkbox3').setValue(false);
101315  *                 }
101316  *             }
101317  *         ],
101318  *         renderTo: Ext.getBody()
101319  *     });
101320  */
101321 Ext.define('Ext.form.field.Checkbox', {
101322     extend: 'Ext.form.field.Base',
101323     alias: ['widget.checkboxfield', 'widget.checkbox'],
101324     alternateClassName: 'Ext.form.Checkbox',
101325     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
101326
101327     // note: {id} here is really {inputId}, but {cmpId} is available
101328     fieldSubTpl: [
101329         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
101330             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101331         '</tpl>',
101332         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
101333         // styled with a custom checkbox image. This allows greater control and consistency in
101334         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
101335         '<input type="button" id="{id}" ',
101336             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
101337             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
101338         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
101339             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101340         '</tpl>',
101341         {
101342             disableFormats: true,
101343             compiled: true
101344         }
101345     ],
101346
101347     isCheckbox: true,
101348
101349     /**
101350      * @cfg {String} [focusCls='x-form-cb-focus']
101351      * The CSS class to use when the checkbox receives focus
101352      */
101353     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
101354
101355     /**
101356      * @cfg {String} [fieldCls='x-form-field']
101357      * The default CSS class for the checkbox
101358      */
101359
101360     /**
101361      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
101362      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
101363      * .
101364      */
101365     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
101366
101367     /**
101368      * @cfg {Boolean} checked
101369      * true if the checkbox should render initially checked
101370      */
101371     checked: false,
101372
101373     /**
101374      * @cfg {String} [checkedCls='x-form-cb-checked']
101375      * The CSS class added to the component's main element when it is in the checked state.
101376      */
101377     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
101378
101379     /**
101380      * @cfg {String} boxLabel
101381      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
101382      * determined by the {@link #boxLabelAlign} config.
101383      */
101384
101385     /**
101386      * @cfg {String} [boxLabelCls='x-form-cb-label']
101387      * The CSS class to be applied to the {@link #boxLabel} element
101388      */
101389     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
101390
101391     /**
101392      * @cfg {String} boxLabelAlign
101393      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
101394      * and 'after'.
101395      */
101396     boxLabelAlign: 'after',
101397
101398     /**
101399      * @cfg {String} inputValue
101400      * The value that should go into the generated input element's value attribute and should be used as the parameter
101401      * value when submitting as part of a form.
101402      */
101403     inputValue: 'on',
101404
101405     /**
101406      * @cfg {String} uncheckedValue
101407      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
101408      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
101409      * submitted (the default behavior of HTML checkboxes).
101410      */
101411
101412     /**
101413      * @cfg {Function} handler
101414      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
101415      * change event}).
101416      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
101417      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
101418      */
101419
101420     /**
101421      * @cfg {Object} scope
101422      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
101423      */
101424
101425     // private overrides
101426     checkChangeEvents: [],
101427     inputType: 'checkbox',
101428     ariaRole: 'checkbox',
101429
101430     // private
101431     onRe: /^on$/i,
101432
101433     initComponent: function(){
101434         this.callParent(arguments);
101435         this.getManager().add(this);
101436     },
101437
101438     initValue: function() {
101439         var me = this,
101440             checked = !!me.checked;
101441
101442         /**
101443          * @property {Object} originalValue
101444          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
101445          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
101446          */
101447         me.originalValue = me.lastValue = checked;
101448
101449         // Set the initial checked state
101450         me.setValue(checked);
101451     },
101452
101453     // private
101454     onRender : function(ct, position) {
101455         var me = this;
101456
101457         /**
101458          * @property {Ext.Element} boxLabelEl
101459          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
101460          * rendered and has a boxLabel configured.
101461          */
101462         me.addChildEls('boxLabelEl');
101463
101464         Ext.applyIf(me.subTplData, {
101465             boxLabel: me.boxLabel,
101466             boxLabelCls: me.boxLabelCls,
101467             boxLabelAlign: me.boxLabelAlign
101468         });
101469
101470         me.callParent(arguments);
101471     },
101472
101473     initEvents: function() {
101474         var me = this;
101475         me.callParent();
101476         me.mon(me.inputEl, 'click', me.onBoxClick, me);
101477     },
101478
101479     /**
101480      * @private Handle click on the checkbox button
101481      */
101482     onBoxClick: function(e) {
101483         var me = this;
101484         if (!me.disabled && !me.readOnly) {
101485             this.setValue(!this.checked);
101486         }
101487     },
101488
101489     /**
101490      * Returns the checked state of the checkbox.
101491      * @return {Boolean} True if checked, else false
101492      */
101493     getRawValue: function() {
101494         return this.checked;
101495     },
101496
101497     /**
101498      * Returns the checked state of the checkbox.
101499      * @return {Boolean} True if checked, else false
101500      */
101501     getValue: function() {
101502         return this.checked;
101503     },
101504
101505     /**
101506      * Returns the submit value for the checkbox which can be used when submitting forms.
101507      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
101508      */
101509     getSubmitValue: function() {
101510         var unchecked = this.uncheckedValue,
101511             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
101512         return this.checked ? this.inputValue : uncheckedVal;
101513     },
101514
101515     /**
101516      * Sets the checked state of the checkbox.
101517      *
101518      * @param {Boolean/String/Number} value The following values will check the checkbox:
101519      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
101520      * Any other value will uncheck the checkbox.
101521      * @return {Boolean} the new checked state of the checkbox
101522      */
101523     setRawValue: function(value) {
101524         var me = this,
101525             inputEl = me.inputEl,
101526             inputValue = me.inputValue,
101527             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
101528                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
101529
101530         if (inputEl) {
101531             inputEl.dom.setAttribute('aria-checked', checked);
101532             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
101533         }
101534
101535         me.checked = me.rawValue = checked;
101536         return checked;
101537     },
101538
101539     /**
101540      * Sets the checked state of the checkbox, and invokes change detection.
101541      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
101542      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
101543      * @return {Ext.form.field.Checkbox} this
101544      */
101545     setValue: function(checked) {
101546         var me = this;
101547
101548         // If an array of strings is passed, find all checkboxes in the group with the same name as this
101549         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
101550         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
101551         // don't want users depending on this behavior.
101552         if (Ext.isArray(checked)) {
101553             me.getManager().getByName(me.name).each(function(cb) {
101554                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
101555             });
101556         } else {
101557             me.callParent(arguments);
101558         }
101559
101560         return me;
101561     },
101562
101563     // private
101564     valueToRaw: function(value) {
101565         // No extra conversion for checkboxes
101566         return value;
101567     },
101568
101569     /**
101570      * @private
101571      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
101572      * function if specified.
101573      */
101574     onChange: function(newVal, oldVal) {
101575         var me = this,
101576             handler = me.handler;
101577         if (handler) {
101578             handler.call(me.scope || me, me, newVal);
101579         }
101580         me.callParent(arguments);
101581     },
101582
101583     // inherit docs
101584     beforeDestroy: function(){
101585         this.callParent();
101586         this.getManager().removeAtKey(this.id);
101587     },
101588
101589     // inherit docs
101590     getManager: function() {
101591         return Ext.form.CheckboxManager;
101592     },
101593
101594     onEnable: function() {
101595         var me = this,
101596             inputEl = me.inputEl;
101597         me.callParent();
101598         if (inputEl) {
101599             // Can still be disabled if the field is readOnly
101600             inputEl.dom.disabled = me.readOnly;
101601         }
101602     },
101603
101604     setReadOnly: function(readOnly) {
101605         var me = this,
101606             inputEl = me.inputEl;
101607         if (inputEl) {
101608             // Set the button to disabled when readonly
101609             inputEl.dom.disabled = readOnly || me.disabled;
101610         }
101611         me.readOnly = readOnly;
101612     },
101613
101614     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
101615     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
101616     getBodyNaturalWidth: function() {
101617         var me = this,
101618             bodyEl = me.bodyEl,
101619             ws = 'white-space',
101620             width;
101621         bodyEl.setStyle(ws, 'nowrap');
101622         width = bodyEl.getWidth();
101623         bodyEl.setStyle(ws, '');
101624         return width;
101625     }
101626
101627 });
101628
101629 /**
101630  * @private
101631  * @class Ext.layout.component.field.Trigger
101632  * @extends Ext.layout.component.field.Field
101633  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
101634  * the trigger button(s).
101635  * @private
101636  */
101637
101638 Ext.define('Ext.layout.component.field.Trigger', {
101639
101640     /* Begin Definitions */
101641
101642     alias: ['layout.triggerfield'],
101643
101644     extend: 'Ext.layout.component.field.Field',
101645
101646     /* End Definitions */
101647
101648     type: 'triggerfield',
101649
101650     sizeBodyContents: function(width, height) {
101651         var me = this,
101652             owner = me.owner,
101653             inputEl = owner.inputEl,
101654             triggerWrap = owner.triggerWrap,
101655             triggerWidth = owner.getTriggerWidth();
101656
101657         // If we or our ancestor is hidden, we can get a triggerWidth calculation
101658         // of 0.  We don't want to resize in this case.
101659         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
101660             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
101661             // are floated left in CSS so they'll stack up side by side.
101662             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
101663     
101664             // Explicitly set the triggerWrap's width, to prevent wrapping
101665             triggerWrap.setWidth(triggerWidth);
101666         }
101667     }
101668 });
101669 /**
101670  * A mechanism for displaying data using custom layout templates and formatting.
101671  *
101672  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
101673  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
101674  * to reflect the changes. The view also provides built-in behavior for many common events that can
101675  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
101676  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
101677  * be provided for the DataView to determine what nodes it will be working with.**
101678  *
101679  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
101680  *
101681  *     @example
101682  *     Ext.define('Image', {
101683  *         extend: 'Ext.data.Model',
101684  *         fields: [
101685  *             { name:'src', type:'string' },
101686  *             { name:'caption', type:'string' }
101687  *         ]
101688  *     });
101689  *
101690  *     Ext.create('Ext.data.Store', {
101691  *         id:'imagesStore',
101692  *         model: 'Image',
101693  *         data: [
101694  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
101695  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
101696  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
101697  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
101698  *         ]
101699  *     });
101700  *
101701  *     var imageTpl = new Ext.XTemplate(
101702  *         '<tpl for=".">',
101703  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
101704  *               '<img src="{src}" />',
101705  *               '<br/><span>{caption}</span>',
101706  *             '</div>',
101707  *         '</tpl>'
101708  *     );
101709  *
101710  *     Ext.create('Ext.view.View', {
101711  *         store: Ext.data.StoreManager.lookup('imagesStore'),
101712  *         tpl: imageTpl,
101713  *         itemSelector: 'div.thumb-wrap',
101714  *         emptyText: 'No images available',
101715  *         renderTo: Ext.getBody()
101716  *     });
101717  */
101718 Ext.define('Ext.view.View', {
101719     extend: 'Ext.view.AbstractView',
101720     alternateClassName: 'Ext.DataView',
101721     alias: 'widget.dataview',
101722
101723     inheritableStatics: {
101724         EventMap: {
101725             mousedown: 'MouseDown',
101726             mouseup: 'MouseUp',
101727             click: 'Click',
101728             dblclick: 'DblClick',
101729             contextmenu: 'ContextMenu',
101730             mouseover: 'MouseOver',
101731             mouseout: 'MouseOut',
101732             mouseenter: 'MouseEnter',
101733             mouseleave: 'MouseLeave',
101734             keydown: 'KeyDown',
101735             focus: 'Focus'
101736         }
101737     },
101738
101739     addCmpEvents: function() {
101740         this.addEvents(
101741             /**
101742              * @event beforeitemmousedown
101743              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
101744              * @param {Ext.view.View} this
101745              * @param {Ext.data.Model} record The record that belongs to the item
101746              * @param {HTMLElement} item The item's element
101747              * @param {Number} index The item's index
101748              * @param {Ext.EventObject} e The raw event object
101749              */
101750             'beforeitemmousedown',
101751             /**
101752              * @event beforeitemmouseup
101753              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
101754              * @param {Ext.view.View} this
101755              * @param {Ext.data.Model} record The record that belongs to the item
101756              * @param {HTMLElement} item The item's element
101757              * @param {Number} index The item's index
101758              * @param {Ext.EventObject} e The raw event object
101759              */
101760             'beforeitemmouseup',
101761             /**
101762              * @event beforeitemmouseenter
101763              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
101764              * @param {Ext.view.View} this
101765              * @param {Ext.data.Model} record The record that belongs to the item
101766              * @param {HTMLElement} item The item's element
101767              * @param {Number} index The item's index
101768              * @param {Ext.EventObject} e The raw event object
101769              */
101770             'beforeitemmouseenter',
101771             /**
101772              * @event beforeitemmouseleave
101773              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
101774              * @param {Ext.view.View} this
101775              * @param {Ext.data.Model} record The record that belongs to the item
101776              * @param {HTMLElement} item The item's element
101777              * @param {Number} index The item's index
101778              * @param {Ext.EventObject} e The raw event object
101779              */
101780             'beforeitemmouseleave',
101781             /**
101782              * @event beforeitemclick
101783              * Fires before the click event on an item is processed. Returns false to cancel the default action.
101784              * @param {Ext.view.View} this
101785              * @param {Ext.data.Model} record The record that belongs to the item
101786              * @param {HTMLElement} item The item's element
101787              * @param {Number} index The item's index
101788              * @param {Ext.EventObject} e The raw event object
101789              */
101790             'beforeitemclick',
101791             /**
101792              * @event beforeitemdblclick
101793              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
101794              * @param {Ext.view.View} this
101795              * @param {Ext.data.Model} record The record that belongs to the item
101796              * @param {HTMLElement} item The item's element
101797              * @param {Number} index The item's index
101798              * @param {Ext.EventObject} e The raw event object
101799              */
101800             'beforeitemdblclick',
101801             /**
101802              * @event beforeitemcontextmenu
101803              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
101804              * @param {Ext.view.View} this
101805              * @param {Ext.data.Model} record The record that belongs to the item
101806              * @param {HTMLElement} item The item's element
101807              * @param {Number} index The item's index
101808              * @param {Ext.EventObject} e The raw event object
101809              */
101810             'beforeitemcontextmenu',
101811             /**
101812              * @event beforeitemkeydown
101813              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
101814              * @param {Ext.view.View} this
101815              * @param {Ext.data.Model} record The record that belongs to the item
101816              * @param {HTMLElement} item The item's element
101817              * @param {Number} index The item's index
101818              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101819              */
101820             'beforeitemkeydown',
101821             /**
101822              * @event itemmousedown
101823              * Fires when there is a mouse down on an item
101824              * @param {Ext.view.View} this
101825              * @param {Ext.data.Model} record The record that belongs to the item
101826              * @param {HTMLElement} item The item's element
101827              * @param {Number} index The item's index
101828              * @param {Ext.EventObject} e The raw event object
101829              */
101830             'itemmousedown',
101831             /**
101832              * @event itemmouseup
101833              * Fires when there is a mouse up on an item
101834              * @param {Ext.view.View} this
101835              * @param {Ext.data.Model} record The record that belongs to the item
101836              * @param {HTMLElement} item The item's element
101837              * @param {Number} index The item's index
101838              * @param {Ext.EventObject} e The raw event object
101839              */
101840             'itemmouseup',
101841             /**
101842              * @event itemmouseenter
101843              * Fires when the mouse enters an item.
101844              * @param {Ext.view.View} this
101845              * @param {Ext.data.Model} record The record that belongs to the item
101846              * @param {HTMLElement} item The item's element
101847              * @param {Number} index The item's index
101848              * @param {Ext.EventObject} e The raw event object
101849              */
101850             'itemmouseenter',
101851             /**
101852              * @event itemmouseleave
101853              * Fires when the mouse leaves an item.
101854              * @param {Ext.view.View} this
101855              * @param {Ext.data.Model} record The record that belongs to the item
101856              * @param {HTMLElement} item The item's element
101857              * @param {Number} index The item's index
101858              * @param {Ext.EventObject} e The raw event object
101859              */
101860             'itemmouseleave',
101861             /**
101862              * @event itemclick
101863              * Fires when an item is clicked.
101864              * @param {Ext.view.View} this
101865              * @param {Ext.data.Model} record The record that belongs to the item
101866              * @param {HTMLElement} item The item's element
101867              * @param {Number} index The item's index
101868              * @param {Ext.EventObject} e The raw event object
101869              */
101870             'itemclick',
101871             /**
101872              * @event itemdblclick
101873              * Fires when an item is double clicked.
101874              * @param {Ext.view.View} this
101875              * @param {Ext.data.Model} record The record that belongs to the item
101876              * @param {HTMLElement} item The item's element
101877              * @param {Number} index The item's index
101878              * @param {Ext.EventObject} e The raw event object
101879              */
101880             'itemdblclick',
101881             /**
101882              * @event itemcontextmenu
101883              * Fires when an item is right clicked.
101884              * @param {Ext.view.View} this
101885              * @param {Ext.data.Model} record The record that belongs to the item
101886              * @param {HTMLElement} item The item's element
101887              * @param {Number} index The item's index
101888              * @param {Ext.EventObject} e The raw event object
101889              */
101890             'itemcontextmenu',
101891             /**
101892              * @event itemkeydown
101893              * Fires when a key is pressed while an item is currently selected.
101894              * @param {Ext.view.View} this
101895              * @param {Ext.data.Model} record The record that belongs to the item
101896              * @param {HTMLElement} item The item's element
101897              * @param {Number} index The item's index
101898              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101899              */
101900             'itemkeydown',
101901             /**
101902              * @event beforecontainermousedown
101903              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
101904              * @param {Ext.view.View} this
101905              * @param {Ext.EventObject} e The raw event object
101906              */
101907             'beforecontainermousedown',
101908             /**
101909              * @event beforecontainermouseup
101910              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
101911              * @param {Ext.view.View} this
101912              * @param {Ext.EventObject} e The raw event object
101913              */
101914             'beforecontainermouseup',
101915             /**
101916              * @event beforecontainermouseover
101917              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
101918              * @param {Ext.view.View} this
101919              * @param {Ext.EventObject} e The raw event object
101920              */
101921             'beforecontainermouseover',
101922             /**
101923              * @event beforecontainermouseout
101924              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
101925              * @param {Ext.view.View} this
101926              * @param {Ext.EventObject} e The raw event object
101927              */
101928             'beforecontainermouseout',
101929             /**
101930              * @event beforecontainerclick
101931              * Fires before the click event on the container is processed. Returns false to cancel the default action.
101932              * @param {Ext.view.View} this
101933              * @param {Ext.EventObject} e The raw event object
101934              */
101935             'beforecontainerclick',
101936             /**
101937              * @event beforecontainerdblclick
101938              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
101939              * @param {Ext.view.View} this
101940              * @param {Ext.EventObject} e The raw event object
101941              */
101942             'beforecontainerdblclick',
101943             /**
101944              * @event beforecontainercontextmenu
101945              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
101946              * @param {Ext.view.View} this
101947              * @param {Ext.EventObject} e The raw event object
101948              */
101949             'beforecontainercontextmenu',
101950             /**
101951              * @event beforecontainerkeydown
101952              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
101953              * @param {Ext.view.View} this
101954              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101955              */
101956             'beforecontainerkeydown',
101957             /**
101958              * @event containermouseup
101959              * Fires when there is a mouse up on the container
101960              * @param {Ext.view.View} this
101961              * @param {Ext.EventObject} e The raw event object
101962              */
101963             'containermouseup',
101964             /**
101965              * @event containermouseover
101966              * Fires when you move the mouse over the container.
101967              * @param {Ext.view.View} this
101968              * @param {Ext.EventObject} e The raw event object
101969              */
101970             'containermouseover',
101971             /**
101972              * @event containermouseout
101973              * Fires when you move the mouse out of the container.
101974              * @param {Ext.view.View} this
101975              * @param {Ext.EventObject} e The raw event object
101976              */
101977             'containermouseout',
101978             /**
101979              * @event containerclick
101980              * Fires when the container is clicked.
101981              * @param {Ext.view.View} this
101982              * @param {Ext.EventObject} e The raw event object
101983              */
101984             'containerclick',
101985             /**
101986              * @event containerdblclick
101987              * Fires when the container is double clicked.
101988              * @param {Ext.view.View} this
101989              * @param {Ext.EventObject} e The raw event object
101990              */
101991             'containerdblclick',
101992             /**
101993              * @event containercontextmenu
101994              * Fires when the container is right clicked.
101995              * @param {Ext.view.View} this
101996              * @param {Ext.EventObject} e The raw event object
101997              */
101998             'containercontextmenu',
101999             /**
102000              * @event containerkeydown
102001              * Fires when a key is pressed while the container is focused, and no item is currently selected.
102002              * @param {Ext.view.View} this
102003              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102004              */
102005             'containerkeydown',
102006
102007             /**
102008              * @event selectionchange
102009              * Fires when the selected nodes change. Relayed event from the underlying selection model.
102010              * @param {Ext.view.View} this
102011              * @param {HTMLElement[]} selections Array of the selected nodes
102012              */
102013             'selectionchange',
102014             /**
102015              * @event beforeselect
102016              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
102017              * @param {Ext.view.View} this
102018              * @param {HTMLElement} node The node to be selected
102019              * @param {HTMLElement[]} selections Array of currently selected nodes
102020              */
102021             'beforeselect'
102022         );
102023     },
102024     // private
102025     afterRender: function(){
102026         var me = this,
102027             listeners;
102028
102029         me.callParent();
102030
102031         listeners = {
102032             scope: me,
102033             /*
102034              * We need to make copies of this since some of the events fired here will end up triggering
102035              * a new event to be called and the shared event object will be mutated. In future we should
102036              * investigate if there are any issues with creating a new event object for each event that
102037              * is fired.
102038              */
102039             freezeEvent: true,
102040             click: me.handleEvent,
102041             mousedown: me.handleEvent,
102042             mouseup: me.handleEvent,
102043             dblclick: me.handleEvent,
102044             contextmenu: me.handleEvent,
102045             mouseover: me.handleEvent,
102046             mouseout: me.handleEvent,
102047             keydown: me.handleEvent
102048         };
102049
102050         me.mon(me.getTargetEl(), listeners);
102051
102052         if (me.store) {
102053             me.bindStore(me.store, true);
102054         }
102055     },
102056
102057     handleEvent: function(e) {
102058         if (this.processUIEvent(e) !== false) {
102059             this.processSpecialEvent(e);
102060         }
102061     },
102062
102063     // Private template method
102064     processItemEvent: Ext.emptyFn,
102065     processContainerEvent: Ext.emptyFn,
102066     processSpecialEvent: Ext.emptyFn,
102067
102068     /*
102069      * Returns true if this mouseover/out event is still over the overItem.
102070      */
102071     stillOverItem: function (event, overItem) {
102072         var nowOver;
102073
102074         // There is this weird bug when you hover over the border of a cell it is saying
102075         // the target is the table.
102076         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
102077         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
102078         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
102079         // this hard exception.
102080         if (overItem && typeof(overItem.offsetParent) === "object") {
102081             // mouseout : relatedTarget == nowOver, target == wasOver
102082             // mouseover: relatedTarget == wasOver, target == nowOver
102083             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
102084             return Ext.fly(overItem).contains(nowOver);
102085         }
102086
102087         return false;
102088     },
102089
102090     processUIEvent: function(e) {
102091         var me = this,
102092             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
102093             map = this.statics().EventMap,
102094             index, record,
102095             type = e.type,
102096             overItem = me.mouseOverItem,
102097             newType;
102098
102099         if (!item) {
102100             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
102101                 item = overItem;
102102             }
102103
102104             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
102105             if (type == 'keydown') {
102106                 record = me.getSelectionModel().getLastSelected();
102107                 if (record) {
102108                     item = me.getNode(record);
102109                 }
102110             }
102111         }
102112
102113         if (item) {
102114             index = me.indexOf(item);
102115             if (!record) {
102116                 record = me.getRecord(item);
102117             }
102118
102119             if (me.processItemEvent(record, item, index, e) === false) {
102120                 return false;
102121             }
102122
102123             newType = me.isNewItemEvent(item, e);
102124             if (newType === false) {
102125                 return false;
102126             }
102127
102128             if (
102129                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
102130                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
102131                 (me['onItem' + map[newType]](record, item, index, e) === false)
102132             ) {
102133                 return false;
102134             }
102135
102136             me.fireEvent('item' + newType, me, record, item, index, e);
102137         }
102138         else {
102139             if (
102140                 (me.processContainerEvent(e) === false) ||
102141                 (me['onBeforeContainer' + map[type]](e) === false) ||
102142                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
102143                 (me['onContainer' + map[type]](e) === false)
102144             ) {
102145                 return false;
102146             }
102147
102148             me.fireEvent('container' + type, me, e);
102149         }
102150
102151         return true;
102152     },
102153
102154     isNewItemEvent: function (item, e) {
102155         var me = this,
102156             overItem = me.mouseOverItem,
102157             type = e.type;
102158
102159         switch (type) {
102160             case 'mouseover':
102161                 if (item === overItem) {
102162                     return false;
102163                 }
102164                 me.mouseOverItem = item;
102165                 return 'mouseenter';
102166
102167             case 'mouseout':
102168                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
102169                 if (me.stillOverItem(e, overItem)) {
102170                     return false;
102171                 }
102172                 me.mouseOverItem = null;
102173                 return 'mouseleave';
102174         }
102175         return type;
102176     },
102177
102178     // private
102179     onItemMouseEnter: function(record, item, index, e) {
102180         if (this.trackOver) {
102181             this.highlightItem(item);
102182         }
102183     },
102184
102185     // private
102186     onItemMouseLeave : function(record, item, index, e) {
102187         if (this.trackOver) {
102188             this.clearHighlight();
102189         }
102190     },
102191
102192     // @private, template methods
102193     onItemMouseDown: Ext.emptyFn,
102194     onItemMouseUp: Ext.emptyFn,
102195     onItemFocus: Ext.emptyFn,
102196     onItemClick: Ext.emptyFn,
102197     onItemDblClick: Ext.emptyFn,
102198     onItemContextMenu: Ext.emptyFn,
102199     onItemKeyDown: Ext.emptyFn,
102200     onBeforeItemMouseDown: Ext.emptyFn,
102201     onBeforeItemMouseUp: Ext.emptyFn,
102202     onBeforeItemFocus: Ext.emptyFn,
102203     onBeforeItemMouseEnter: Ext.emptyFn,
102204     onBeforeItemMouseLeave: Ext.emptyFn,
102205     onBeforeItemClick: Ext.emptyFn,
102206     onBeforeItemDblClick: Ext.emptyFn,
102207     onBeforeItemContextMenu: Ext.emptyFn,
102208     onBeforeItemKeyDown: Ext.emptyFn,
102209
102210     // @private, template methods
102211     onContainerMouseDown: Ext.emptyFn,
102212     onContainerMouseUp: Ext.emptyFn,
102213     onContainerMouseOver: Ext.emptyFn,
102214     onContainerMouseOut: Ext.emptyFn,
102215     onContainerClick: Ext.emptyFn,
102216     onContainerDblClick: Ext.emptyFn,
102217     onContainerContextMenu: Ext.emptyFn,
102218     onContainerKeyDown: Ext.emptyFn,
102219     onBeforeContainerMouseDown: Ext.emptyFn,
102220     onBeforeContainerMouseUp: Ext.emptyFn,
102221     onBeforeContainerMouseOver: Ext.emptyFn,
102222     onBeforeContainerMouseOut: Ext.emptyFn,
102223     onBeforeContainerClick: Ext.emptyFn,
102224     onBeforeContainerDblClick: Ext.emptyFn,
102225     onBeforeContainerContextMenu: Ext.emptyFn,
102226     onBeforeContainerKeyDown: Ext.emptyFn,
102227
102228     /**
102229      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
102230      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
102231      * handle stepping through the list via keyboard navigation.
102232      * @param {HTMLElement} item The item to highlight
102233      */
102234     highlightItem: function(item) {
102235         var me = this;
102236         me.clearHighlight();
102237         me.highlightedItem = item;
102238         Ext.fly(item).addCls(me.overItemCls);
102239     },
102240
102241     /**
102242      * Un-highlights the currently highlighted item, if any.
102243      */
102244     clearHighlight: function() {
102245         var me = this,
102246             highlighted = me.highlightedItem;
102247
102248         if (highlighted) {
102249             Ext.fly(highlighted).removeCls(me.overItemCls);
102250             delete me.highlightedItem;
102251         }
102252     },
102253
102254     refresh: function() {
102255         var me = this;
102256         me.clearHighlight();
102257         me.callParent(arguments);
102258         if (!me.isFixedHeight()) {
102259             me.doComponentLayout();
102260         }
102261     }
102262 });
102263 /**
102264  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
102265  * @class Ext.layout.component.BoundList
102266  * @extends Ext.layout.component.Component
102267  * @private
102268  */
102269 Ext.define('Ext.layout.component.BoundList', {
102270     extend: 'Ext.layout.component.Component',
102271     alias: 'layout.boundlist',
102272
102273     type: 'component',
102274
102275     beforeLayout: function() {
102276         return this.callParent(arguments) || this.owner.refreshed > 0;
102277     },
102278
102279     onLayout : function(width, height) {
102280         var me = this,
102281             owner = me.owner,
102282             floating = owner.floating,
102283             el = owner.el,
102284             xy = el.getXY(),
102285             isNumber = Ext.isNumber,
102286             minWidth, maxWidth, minHeight, maxHeight,
102287             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
102288
102289         if (floating) {
102290             // Position offscreen so the natural width is not affected by the viewport's right edge
102291             el.setXY([-9999,-9999]);
102292         }
102293
102294         // Calculate initial layout
102295         me.setTargetSize(width, height);
102296
102297         // Handle min/maxWidth for auto-width
102298         if (!isNumber(width)) {
102299             minWidth = owner.minWidth;
102300             maxWidth = owner.maxWidth;
102301             if (isNumber(minWidth) || isNumber(maxWidth)) {
102302                 naturalWidth = el.getWidth();
102303                 if (naturalWidth < minWidth) {
102304                     constrainedWidth = minWidth;
102305                 }
102306                 else if (naturalWidth > maxWidth) {
102307                     constrainedWidth = maxWidth;
102308                 }
102309                 if (constrainedWidth) {
102310                     me.setTargetSize(constrainedWidth);
102311                 }
102312             }
102313         }
102314         // Handle min/maxHeight for auto-height
102315         if (!isNumber(height)) {
102316             minHeight = owner.minHeight;
102317             maxHeight = owner.maxHeight;
102318             if (isNumber(minHeight) || isNumber(maxHeight)) {
102319                 naturalHeight = el.getHeight();
102320                 if (naturalHeight < minHeight) {
102321                     constrainedHeight = minHeight;
102322                 }
102323                 else if (naturalHeight > maxHeight) {
102324                     constrainedHeight = maxHeight;
102325                 }
102326                 if (constrainedHeight) {
102327                     me.setTargetSize(undef, constrainedHeight);
102328                 }
102329             }
102330         }
102331
102332         if (floating) {
102333             // Restore position
102334             el.setXY(xy);
102335         }
102336     },
102337
102338     afterLayout: function() {
102339         var me = this,
102340             toolbar = me.owner.pagingToolbar;
102341         me.callParent();
102342         if (toolbar) {
102343             toolbar.doComponentLayout();
102344         }
102345     },
102346
102347     setTargetSize : function(width, height) {
102348         var me = this,
102349             owner = me.owner,
102350             listHeight = null,
102351             toolbar;
102352
102353         // Size the listEl
102354         if (Ext.isNumber(height)) {
102355             listHeight = height - owner.el.getFrameWidth('tb');
102356             toolbar = owner.pagingToolbar;
102357             if (toolbar) {
102358                 listHeight -= toolbar.getHeight();
102359             }
102360         }
102361         me.setElementSize(owner.listEl, null, listHeight);
102362
102363         me.callParent(arguments);
102364     }
102365
102366 });
102367
102368 /**
102369  * A simple class that renders text directly into a toolbar.
102370  *
102371  *     @example
102372  *     Ext.create('Ext.panel.Panel', {
102373  *         title: 'Panel with TextItem',
102374  *         width: 300,
102375  *         height: 200,
102376  *         tbar: [
102377  *             { xtype: 'tbtext', text: 'Sample Text Item' }
102378  *         ],
102379  *         renderTo: Ext.getBody()
102380  *     });
102381  *
102382  * @constructor
102383  * Creates a new TextItem
102384  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
102385  */
102386 Ext.define('Ext.toolbar.TextItem', {
102387     extend: 'Ext.toolbar.Item',
102388     requires: ['Ext.XTemplate'],
102389     alias: 'widget.tbtext',
102390     alternateClassName: 'Ext.Toolbar.TextItem',
102391
102392     /**
102393      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
102394      */
102395     text: '',
102396
102397     renderTpl: '{text}',
102398     //
102399     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
102400
102401     onRender : function() {
102402         Ext.apply(this.renderData, {
102403             text: this.text
102404         });
102405         this.callParent(arguments);
102406     },
102407
102408     /**
102409      * Updates this item's text, setting the text to be used as innerHTML.
102410      * @param {String} t The text to display (html accepted).
102411      */
102412     setText : function(t) {
102413         if (this.rendered) {
102414             this.el.update(t);
102415             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
102416         } else {
102417             this.text = t;
102418         }
102419     }
102420 });
102421 /**
102422  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
102423  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
102424  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
102425  * can provide a custom implementation.
102426  *
102427  * For example:
102428  *
102429  *     @example
102430  *     Ext.define('Ext.ux.CustomTrigger', {
102431  *         extend: 'Ext.form.field.Trigger',
102432  *         alias: 'widget.customtrigger',
102433  *
102434  *         // override onTriggerClick
102435  *         onTriggerClick: function() {
102436  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
102437  *         }
102438  *     });
102439  *
102440  *     Ext.create('Ext.form.FormPanel', {
102441  *         title: 'Form with TriggerField',
102442  *         bodyPadding: 5,
102443  *         width: 350,
102444  *         renderTo: Ext.getBody(),
102445  *         items:[{
102446  *             xtype: 'customtrigger',
102447  *             fieldLabel: 'Sample Trigger',
102448  *             emptyText: 'click the trigger',
102449  *         }]
102450  *     });
102451  *
102452  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
102453  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
102454  */
102455 Ext.define('Ext.form.field.Trigger', {
102456     extend:'Ext.form.field.Text',
102457     alias: ['widget.triggerfield', 'widget.trigger'],
102458     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
102459     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
102460
102461     // note: {id} here is really {inputId}, but {cmpId} is available
102462     fieldSubTpl: [
102463         '<input id="{id}" type="{type}" ',
102464             '<tpl if="name">name="{name}" </tpl>',
102465             '<tpl if="size">size="{size}" </tpl>',
102466             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
102467             'class="{fieldCls} {typeCls}" autocomplete="off" />',
102468         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
102469             '{triggerEl}',
102470             '<div class="{clearCls}" role="presentation"></div>',
102471         '</div>',
102472         {
102473             compiled: true,
102474             disableFormats: true
102475         }
102476     ],
102477
102478     /**
102479      * @cfg {String} triggerCls
102480      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
102481      * by default and triggerCls will be **appended** if specified.
102482      */
102483
102484     /**
102485      * @cfg {String} [triggerBaseCls='x-form-trigger']
102486      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
102487      * addition to this class.
102488      */
102489     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
102490
102491     /**
102492      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
102493      * The CSS class that is added to the div wrapping the trigger button(s).
102494      */
102495     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
102496
102497     /**
102498      * @cfg {Boolean} hideTrigger
102499      * true to hide the trigger element and display only the base text field
102500      */
102501     hideTrigger: false,
102502
102503     /**
102504      * @cfg {Boolean} editable
102505      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
102506      * action invoked by the trigger.
102507      */
102508     editable: true,
102509
102510     /**
102511      * @cfg {Boolean} readOnly
102512      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
102513      * options if the value is true.
102514      */
102515     readOnly: false,
102516
102517     /**
102518      * @cfg {Boolean} [selectOnFocus=false]
102519      * true to select any existing text in the field immediately on focus. Only applies when
102520      * {@link #editable editable} = true
102521      */
102522
102523     /**
102524      * @cfg {Boolean} repeatTriggerClick
102525      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
102526      */
102527     repeatTriggerClick: false,
102528
102529
102530     /**
102531      * @hide
102532      * @method autoSize
102533      */
102534     autoSize: Ext.emptyFn,
102535     // private
102536     monitorTab: true,
102537     // private
102538     mimicing: false,
102539     // private
102540     triggerIndexRe: /trigger-index-(\d+)/,
102541
102542     componentLayout: 'triggerfield',
102543
102544     initComponent: function() {
102545         this.wrapFocusCls = this.triggerWrapCls + '-focus';
102546         this.callParent(arguments);
102547     },
102548
102549     // private
102550     onRender: function(ct, position) {
102551         var me = this,
102552             triggerCls,
102553             triggerBaseCls = me.triggerBaseCls,
102554             triggerWrapCls = me.triggerWrapCls,
102555             triggerConfigs = [],
102556             i;
102557
102558         // triggerCls is a synonym for trigger1Cls, so copy it.
102559         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
102560         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
102561         // trigger config objects that hold cls, handler, etc.
102562         if (!me.trigger1Cls) {
102563             me.trigger1Cls = me.triggerCls;
102564         }
102565
102566         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
102567         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
102568             triggerConfigs.push({
102569                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
102570                 role: 'button'
102571             });
102572         }
102573         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
102574
102575         /**
102576          * @property {Ext.Element} triggerWrap
102577          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
102578          */
102579         me.addChildEls('triggerWrap');
102580
102581         Ext.applyIf(me.subTplData, {
102582             triggerWrapCls: triggerWrapCls,
102583             triggerEl: Ext.DomHelper.markup(triggerConfigs),
102584             clearCls: me.clearCls
102585         });
102586
102587         me.callParent(arguments);
102588
102589         /**
102590          * @property {Ext.CompositeElement} triggerEl
102591          * A composite of all the trigger button elements. Only set after the field has been rendered.
102592          */
102593         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
102594
102595         me.doc = Ext.getDoc();
102596         me.initTrigger();
102597     },
102598
102599     onEnable: function() {
102600         this.callParent();
102601         this.triggerWrap.unmask();
102602     },
102603     
102604     onDisable: function() {
102605         this.callParent();
102606         this.triggerWrap.mask();
102607     },
102608     
102609     afterRender: function() {
102610         this.callParent();
102611         this.updateEditState();
102612         this.triggerEl.unselectable();
102613     },
102614
102615     updateEditState: function() {
102616         var me = this,
102617             inputEl = me.inputEl,
102618             triggerWrap = me.triggerWrap,
102619             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
102620             displayed,
102621             readOnly;
102622
102623         if (me.rendered) {
102624             if (me.readOnly) {
102625                 inputEl.addCls(noeditCls);
102626                 readOnly = true;
102627                 displayed = false;
102628             } else {
102629                 if (me.editable) {
102630                     inputEl.removeCls(noeditCls);
102631                     readOnly = false;
102632                 } else {
102633                     inputEl.addCls(noeditCls);
102634                     readOnly = true;
102635                 }
102636                 displayed = !me.hideTrigger;
102637             }
102638
102639             triggerWrap.setDisplayed(displayed);
102640             inputEl.dom.readOnly = readOnly;
102641             me.doComponentLayout();
102642         }
102643     },
102644
102645     /**
102646      * Get the total width of the trigger button area. Only useful after the field has been rendered.
102647      * @return {Number} The trigger width
102648      */
102649     getTriggerWidth: function() {
102650         var me = this,
102651             triggerWrap = me.triggerWrap,
102652             totalTriggerWidth = 0;
102653         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
102654             me.triggerEl.each(function(trigger) {
102655                 totalTriggerWidth += trigger.getWidth();
102656             });
102657             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
102658         }
102659         return totalTriggerWidth;
102660     },
102661
102662     setHideTrigger: function(hideTrigger) {
102663         if (hideTrigger != this.hideTrigger) {
102664             this.hideTrigger = hideTrigger;
102665             this.updateEditState();
102666         }
102667     },
102668
102669     /**
102670      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
102671      * option at config time.
102672      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
102673      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
102674      * will call the trigger. 
102675      */
102676     setEditable: function(editable) {
102677         if (editable != this.editable) {
102678             this.editable = editable;
102679             this.updateEditState();
102680         }
102681     },
102682
102683     /**
102684      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
102685      * option at config time.
102686      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
102687      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
102688      * and hideTrigger.
102689      */
102690     setReadOnly: function(readOnly) {
102691         if (readOnly != this.readOnly) {
102692             this.readOnly = readOnly;
102693             this.updateEditState();
102694         }
102695     },
102696
102697     // private
102698     initTrigger: function() {
102699         var me = this,
102700             triggerWrap = me.triggerWrap,
102701             triggerEl = me.triggerEl;
102702
102703         if (me.repeatTriggerClick) {
102704             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
102705                 preventDefault: true,
102706                 handler: function(cr, e) {
102707                     me.onTriggerWrapClick(e);
102708                 }
102709             });
102710         } else {
102711             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
102712         }
102713
102714         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
102715         triggerEl.each(function(el, c, i) {
102716             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
102717         });
102718         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
102719         triggerEl.each(function(el, c, i) {
102720             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
102721         });
102722     },
102723
102724     // private
102725     onDestroy: function() {
102726         var me = this;
102727         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
102728         delete me.doc;
102729         me.callParent();
102730     },
102731
102732     // private
102733     onFocus: function() {
102734         var me = this;
102735         me.callParent();
102736         if (!me.mimicing) {
102737             me.bodyEl.addCls(me.wrapFocusCls);
102738             me.mimicing = true;
102739             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
102740                 delay: 10
102741             });
102742             if (me.monitorTab) {
102743                 me.on('specialkey', me.checkTab, me);
102744             }
102745         }
102746     },
102747
102748     // private
102749     checkTab: function(me, e) {
102750         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
102751             this.triggerBlur();
102752         }
102753     },
102754
102755     // private
102756     onBlur: Ext.emptyFn,
102757
102758     // private
102759     mimicBlur: function(e) {
102760         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
102761             this.triggerBlur();
102762         }
102763     },
102764
102765     // private
102766     triggerBlur: function() {
102767         var me = this;
102768         me.mimicing = false;
102769         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
102770         if (me.monitorTab && me.inputEl) {
102771             me.un('specialkey', me.checkTab, me);
102772         }
102773         Ext.form.field.Trigger.superclass.onBlur.call(me);
102774         if (me.bodyEl) {
102775             me.bodyEl.removeCls(me.wrapFocusCls);
102776         }
102777     },
102778
102779     beforeBlur: Ext.emptyFn,
102780
102781     // private
102782     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
102783     validateBlur: function(e) {
102784         return true;
102785     },
102786
102787     // private
102788     // process clicks upon triggers.
102789     // determine which trigger index, and dispatch to the appropriate click handler
102790     onTriggerWrapClick: function(e) {
102791         var me = this,
102792             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
102793             match = t && t.className.match(me.triggerIndexRe),
102794             idx,
102795             triggerClickMethod;
102796
102797         if (match && !me.readOnly) {
102798             idx = parseInt(match[1], 10);
102799             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
102800             if (triggerClickMethod) {
102801                 triggerClickMethod.call(me, e);
102802             }
102803         }
102804     },
102805
102806     /**
102807      * @method onTriggerClick
102808      * @protected
102809      * The function that should handle the trigger's click event. This method does nothing by default until overridden
102810      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
102811      * @param {Ext.EventObject} e
102812      */
102813     onTriggerClick: Ext.emptyFn
102814
102815     /**
102816      * @cfg {Boolean} grow @hide
102817      */
102818     /**
102819      * @cfg {Number} growMin @hide
102820      */
102821     /**
102822      * @cfg {Number} growMax @hide
102823      */
102824 });
102825
102826 /**
102827  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
102828  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
102829  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
102830  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
102831  * respectively.
102832  *
102833  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
102834  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
102835  * for the field.
102836  */
102837 Ext.define('Ext.form.field.Picker', {
102838     extend: 'Ext.form.field.Trigger',
102839     alias: 'widget.pickerfield',
102840     alternateClassName: 'Ext.form.Picker',
102841     requires: ['Ext.util.KeyNav'],
102842
102843     /**
102844      * @cfg {Boolean} matchFieldWidth
102845      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
102846      */
102847     matchFieldWidth: true,
102848
102849     /**
102850      * @cfg {String} pickerAlign
102851      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
102852      */
102853     pickerAlign: 'tl-bl?',
102854
102855     /**
102856      * @cfg {Number[]} pickerOffset
102857      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
102858      * Defaults to undefined.
102859      */
102860
102861     /**
102862      * @cfg {String} openCls
102863      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
102864      * Defaults to 'x-pickerfield-open'.
102865      */
102866     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
102867
102868     /**
102869      * @property {Boolean} isExpanded
102870      * True if the picker is currently expanded, false if not.
102871      */
102872
102873     /**
102874      * @cfg {Boolean} editable
102875      * False to prevent the user from typing text directly into the field; the field can only have its value set via
102876      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
102877      * field itself.
102878      */
102879     editable: true,
102880
102881
102882     initComponent: function() {
102883         this.callParent();
102884
102885         // Custom events
102886         this.addEvents(
102887             /**
102888              * @event expand
102889              * Fires when the field's picker is expanded.
102890              * @param {Ext.form.field.Picker} field This field instance
102891              */
102892             'expand',
102893             /**
102894              * @event collapse
102895              * Fires when the field's picker is collapsed.
102896              * @param {Ext.form.field.Picker} field This field instance
102897              */
102898             'collapse',
102899             /**
102900              * @event select
102901              * Fires when a value is selected via the picker.
102902              * @param {Ext.form.field.Picker} field This field instance
102903              * @param {Object} value The value that was selected. The exact type of this value is dependent on
102904              * the individual field and picker implementations.
102905              */
102906             'select'
102907         );
102908     },
102909
102910
102911     initEvents: function() {
102912         var me = this;
102913         me.callParent();
102914
102915         // Add handlers for keys to expand/collapse the picker
102916         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
102917             down: function() {
102918                 if (!me.isExpanded) {
102919                     // Don't call expand() directly as there may be additional processing involved before
102920                     // expanding, e.g. in the case of a ComboBox query.
102921                     me.onTriggerClick();
102922                 }
102923             },
102924             esc: me.collapse,
102925             scope: me,
102926             forceKeyDown: true
102927         });
102928
102929         // Non-editable allows opening the picker by clicking the field
102930         if (!me.editable) {
102931             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
102932         }
102933
102934         // Disable native browser autocomplete
102935         if (Ext.isGecko) {
102936             me.inputEl.dom.setAttribute('autocomplete', 'off');
102937         }
102938     },
102939
102940
102941     /**
102942      * Expands this field's picker dropdown.
102943      */
102944     expand: function() {
102945         var me = this,
102946             bodyEl, picker, collapseIf;
102947
102948         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
102949             bodyEl = me.bodyEl;
102950             picker = me.getPicker();
102951             collapseIf = me.collapseIf;
102952
102953             // show the picker and set isExpanded flag
102954             picker.show();
102955             me.isExpanded = true;
102956             me.alignPicker();
102957             bodyEl.addCls(me.openCls);
102958
102959             // monitor clicking and mousewheel
102960             me.mon(Ext.getDoc(), {
102961                 mousewheel: collapseIf,
102962                 mousedown: collapseIf,
102963                 scope: me
102964             });
102965             Ext.EventManager.onWindowResize(me.alignPicker, me);
102966             me.fireEvent('expand', me);
102967             me.onExpand();
102968         }
102969     },
102970
102971     onExpand: Ext.emptyFn,
102972
102973     /**
102974      * Aligns the picker to the input element
102975      * @protected
102976      */
102977     alignPicker: function() {
102978         var me = this,
102979             picker;
102980
102981         if (me.isExpanded) {
102982             picker = me.getPicker();
102983             if (me.matchFieldWidth) {
102984                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
102985                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
102986             }
102987             if (picker.isFloating()) {
102988                 me.doAlign();
102989             }
102990         }
102991     },
102992
102993     /**
102994      * Performs the alignment on the picker using the class defaults
102995      * @private
102996      */
102997     doAlign: function(){
102998         var me = this,
102999             picker = me.picker,
103000             aboveSfx = '-above',
103001             isAbove;
103002
103003         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
103004         // add the {openCls}-above class if the picker was aligned above
103005         // the field due to hitting the bottom of the viewport
103006         isAbove = picker.el.getY() < me.inputEl.getY();
103007         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
103008         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
103009     },
103010
103011     /**
103012      * Collapses this field's picker dropdown.
103013      */
103014     collapse: function() {
103015         if (this.isExpanded && !this.isDestroyed) {
103016             var me = this,
103017                 openCls = me.openCls,
103018                 picker = me.picker,
103019                 doc = Ext.getDoc(),
103020                 collapseIf = me.collapseIf,
103021                 aboveSfx = '-above';
103022
103023             // hide the picker and set isExpanded flag
103024             picker.hide();
103025             me.isExpanded = false;
103026
103027             // remove the openCls
103028             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
103029             picker.el.removeCls(picker.baseCls + aboveSfx);
103030
103031             // remove event listeners
103032             doc.un('mousewheel', collapseIf, me);
103033             doc.un('mousedown', collapseIf, me);
103034             Ext.EventManager.removeResizeListener(me.alignPicker, me);
103035             me.fireEvent('collapse', me);
103036             me.onCollapse();
103037         }
103038     },
103039
103040     onCollapse: Ext.emptyFn,
103041
103042
103043     /**
103044      * @private
103045      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
103046      */
103047     collapseIf: function(e) {
103048         var me = this;
103049         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
103050             me.collapse();
103051         }
103052     },
103053
103054     /**
103055      * Returns a reference to the picker component for this field, creating it if necessary by
103056      * calling {@link #createPicker}.
103057      * @return {Ext.Component} The picker component
103058      */
103059     getPicker: function() {
103060         var me = this;
103061         return me.picker || (me.picker = me.createPicker());
103062     },
103063
103064     /**
103065      * @method
103066      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
103067      * The current field should also be passed as a configuration option to the picker component as the pickerField
103068      * property.
103069      */
103070     createPicker: Ext.emptyFn,
103071
103072     /**
103073      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
103074      * @protected
103075      */
103076     onTriggerClick: function() {
103077         var me = this;
103078         if (!me.readOnly && !me.disabled) {
103079             if (me.isExpanded) {
103080                 me.collapse();
103081             } else {
103082                 me.expand();
103083             }
103084             me.inputEl.focus();
103085         }
103086     },
103087
103088     mimicBlur: function(e) {
103089         var me = this,
103090             picker = me.picker;
103091         // ignore mousedown events within the picker element
103092         if (!picker || !e.within(picker.el, false, true)) {
103093             me.callParent(arguments);
103094         }
103095     },
103096
103097     onDestroy : function(){
103098         var me = this,
103099             picker = me.picker;
103100
103101         Ext.EventManager.removeResizeListener(me.alignPicker, me);
103102         Ext.destroy(me.keyNav);
103103         if (picker) {
103104             delete picker.pickerField;
103105             picker.destroy();
103106         }
103107         me.callParent();
103108     }
103109
103110 });
103111
103112
103113 /**
103114  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
103115  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
103116  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
103117  * field which uses the spinner to increment and decrement the field's value by its
103118  * {@link Ext.form.field.Number#step step} config value.
103119  *
103120  * For example:
103121  *
103122  *     @example
103123  *     Ext.define('Ext.ux.CustomSpinner', {
103124  *         extend: 'Ext.form.field.Spinner',
103125  *         alias: 'widget.customspinner',
103126  *
103127  *         // override onSpinUp (using step isn't neccessary)
103128  *         onSpinUp: function() {
103129  *             var me = this;
103130  *             if (!me.readOnly) {
103131  *                 var val = me.step; // set the default value to the step value
103132  *                 if(me.getValue() !== '') {
103133  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103134  *                 }
103135  *                 me.setValue((val + me.step) + ' Pack');
103136  *             }
103137  *         },
103138  *
103139  *         // override onSpinDown
103140  *         onSpinDown: function() {
103141  *             var val, me = this;
103142  *             if (!me.readOnly) {
103143  *                 if(me.getValue() !== '') {
103144  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103145  *                 }
103146  *                 me.setValue((val - me.step) + ' Pack');
103147  *             }
103148  *         }
103149  *     });
103150  *
103151  *     Ext.create('Ext.form.FormPanel', {
103152  *         title: 'Form with SpinnerField',
103153  *         bodyPadding: 5,
103154  *         width: 350,
103155  *         renderTo: Ext.getBody(),
103156  *         items:[{
103157  *             xtype: 'customspinner',
103158  *             fieldLabel: 'How Much Beer?',
103159  *             step: 6
103160  *         }]
103161  *     });
103162  *
103163  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
103164  * to prevent this, set `{@link #keyNavEnabled} = false`.
103165  */
103166 Ext.define('Ext.form.field.Spinner', {
103167     extend: 'Ext.form.field.Trigger',
103168     alias: 'widget.spinnerfield',
103169     alternateClassName: 'Ext.form.Spinner',
103170     requires: ['Ext.util.KeyNav'],
103171
103172     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
103173     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
103174
103175     /**
103176      * @cfg {Boolean} spinUpEnabled
103177      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
103178      * created, use the {@link #setSpinUpEnabled} method.
103179      */
103180     spinUpEnabled: true,
103181
103182     /**
103183      * @cfg {Boolean} spinDownEnabled
103184      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
103185      * created, use the {@link #setSpinDownEnabled} method.
103186      */
103187     spinDownEnabled: true,
103188
103189     /**
103190      * @cfg {Boolean} keyNavEnabled
103191      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
103192      */
103193     keyNavEnabled: true,
103194
103195     /**
103196      * @cfg {Boolean} mouseWheelEnabled
103197      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
103198      * Defaults to true.
103199      */
103200     mouseWheelEnabled: true,
103201
103202     /**
103203      * @cfg {Boolean} repeatTriggerClick
103204      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
103205      * Defaults to true.
103206      */
103207     repeatTriggerClick: true,
103208
103209     /**
103210      * @method
103211      * @protected
103212      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
103213      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103214      */
103215     onSpinUp: Ext.emptyFn,
103216
103217     /**
103218      * @method
103219      * @protected
103220      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
103221      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103222      */
103223     onSpinDown: Ext.emptyFn,
103224
103225     initComponent: function() {
103226         this.callParent();
103227
103228         this.addEvents(
103229             /**
103230              * @event spin
103231              * Fires when the spinner is made to spin up or down.
103232              * @param {Ext.form.field.Spinner} this
103233              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
103234              */
103235             'spin',
103236
103237             /**
103238              * @event spinup
103239              * Fires when the spinner is made to spin up.
103240              * @param {Ext.form.field.Spinner} this
103241              */
103242             'spinup',
103243
103244             /**
103245              * @event spindown
103246              * Fires when the spinner is made to spin down.
103247              * @param {Ext.form.field.Spinner} this
103248              */
103249             'spindown'
103250         );
103251     },
103252
103253     /**
103254      * @private
103255      * Override.
103256      */
103257     onRender: function() {
103258         var me = this,
103259             triggers;
103260
103261         me.callParent(arguments);
103262         triggers = me.triggerEl;
103263
103264         /**
103265          * @property {Ext.Element} spinUpEl
103266          * The spinner up button element
103267          */
103268         me.spinUpEl = triggers.item(0);
103269         /**
103270          * @property {Ext.Element} spinDownEl
103271          * The spinner down button element
103272          */
103273         me.spinDownEl = triggers.item(1);
103274
103275         // Set initial enabled/disabled states
103276         me.setSpinUpEnabled(me.spinUpEnabled);
103277         me.setSpinDownEnabled(me.spinDownEnabled);
103278
103279         // Init up/down arrow keys
103280         if (me.keyNavEnabled) {
103281             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
103282                 scope: me,
103283                 up: me.spinUp,
103284                 down: me.spinDown
103285             });
103286         }
103287
103288         // Init mouse wheel
103289         if (me.mouseWheelEnabled) {
103290             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
103291         }
103292     },
103293
103294     /**
103295      * @private
103296      * Override. Since the triggers are stacked, only measure the width of one of them.
103297      */
103298     getTriggerWidth: function() {
103299         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
103300     },
103301
103302     /**
103303      * @private
103304      * Handles the spinner up button clicks.
103305      */
103306     onTrigger1Click: function() {
103307         this.spinUp();
103308     },
103309
103310     /**
103311      * @private
103312      * Handles the spinner down button clicks.
103313      */
103314     onTrigger2Click: function() {
103315         this.spinDown();
103316     },
103317
103318     /**
103319      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
103320      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
103321      * is false.
103322      */
103323     spinUp: function() {
103324         var me = this;
103325         if (me.spinUpEnabled && !me.disabled) {
103326             me.fireEvent('spin', me, 'up');
103327             me.fireEvent('spinup', me);
103328             me.onSpinUp();
103329         }
103330     },
103331
103332     /**
103333      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
103334      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
103335      * is false.
103336      */
103337     spinDown: function() {
103338         var me = this;
103339         if (me.spinDownEnabled && !me.disabled) {
103340             me.fireEvent('spin', me, 'down');
103341             me.fireEvent('spindown', me);
103342             me.onSpinDown();
103343         }
103344     },
103345
103346     /**
103347      * Sets whether the spinner up button is enabled.
103348      * @param {Boolean} enabled true to enable the button, false to disable it.
103349      */
103350     setSpinUpEnabled: function(enabled) {
103351         var me = this,
103352             wasEnabled = me.spinUpEnabled;
103353         me.spinUpEnabled = enabled;
103354         if (wasEnabled !== enabled && me.rendered) {
103355             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
103356         }
103357     },
103358
103359     /**
103360      * Sets whether the spinner down button is enabled.
103361      * @param {Boolean} enabled true to enable the button, false to disable it.
103362      */
103363     setSpinDownEnabled: function(enabled) {
103364         var me = this,
103365             wasEnabled = me.spinDownEnabled;
103366         me.spinDownEnabled = enabled;
103367         if (wasEnabled !== enabled && me.rendered) {
103368             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
103369         }
103370     },
103371
103372     /**
103373      * @private
103374      * Handles mousewheel events on the field
103375      */
103376     onMouseWheel: function(e) {
103377         var me = this,
103378             delta;
103379         if (me.hasFocus) {
103380             delta = e.getWheelDelta();
103381             if (delta > 0) {
103382                 me.spinUp();
103383             }
103384             else if (delta < 0) {
103385                 me.spinDown();
103386             }
103387             e.stopEvent();
103388         }
103389     },
103390
103391     onDestroy: function() {
103392         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
103393         this.callParent();
103394     }
103395
103396 });
103397 /**
103398  * @docauthor Jason Johnston <jason@sencha.com>
103399  *
103400  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
103401  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
103402  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
103403  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
103404  *
103405  * By default, the number field is also rendered with a set of up/down spinner buttons and has
103406  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
103407  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
103408  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
103409  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
103410  *
103411  * # Example usage
103412  *
103413  *     @example
103414  *     Ext.create('Ext.form.Panel', {
103415  *         title: 'On The Wall',
103416  *         width: 300,
103417  *         bodyPadding: 10,
103418  *         renderTo: Ext.getBody(),
103419  *         items: [{
103420  *             xtype: 'numberfield',
103421  *             anchor: '100%',
103422  *             name: 'bottles',
103423  *             fieldLabel: 'Bottles of Beer',
103424  *             value: 99,
103425  *             maxValue: 99,
103426  *             minValue: 0
103427  *         }],
103428  *         buttons: [{
103429  *             text: 'Take one down, pass it around',
103430  *             handler: function() {
103431  *                 this.up('form').down('[name=bottles]').spinDown();
103432  *             }
103433  *         }]
103434  *     });
103435  *
103436  * # Removing UI Enhancements
103437  *
103438  *     @example
103439  *     Ext.create('Ext.form.Panel', {
103440  *         title: 'Personal Info',
103441  *         width: 300,
103442  *         bodyPadding: 10,
103443  *         renderTo: Ext.getBody(),
103444  *         items: [{
103445  *             xtype: 'numberfield',
103446  *             anchor: '100%',
103447  *             name: 'age',
103448  *             fieldLabel: 'Age',
103449  *             minValue: 0, //prevents negative numbers
103450  *
103451  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
103452  *             hideTrigger: true,
103453  *             keyNavEnabled: false,
103454  *             mouseWheelEnabled: false
103455  *         }]
103456  *     });
103457  *
103458  * # Using Step
103459  *
103460  *     @example
103461  *     Ext.create('Ext.form.Panel', {
103462  *         renderTo: Ext.getBody(),
103463  *         title: 'Step',
103464  *         width: 300,
103465  *         bodyPadding: 10,
103466  *         items: [{
103467  *             xtype: 'numberfield',
103468  *             anchor: '100%',
103469  *             name: 'evens',
103470  *             fieldLabel: 'Even Numbers',
103471  *
103472  *             // Set step so it skips every other number
103473  *             step: 2,
103474  *             value: 0,
103475  *
103476  *             // Add change handler to force user-entered numbers to evens
103477  *             listeners: {
103478  *                 change: function(field, value) {
103479  *                     value = parseInt(value, 10);
103480  *                     field.setValue(value + value % 2);
103481  *                 }
103482  *             }
103483  *         }]
103484  *     });
103485  */
103486 Ext.define('Ext.form.field.Number', {
103487     extend:'Ext.form.field.Spinner',
103488     alias: 'widget.numberfield',
103489     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
103490
103491     /**
103492      * @cfg {RegExp} stripCharsRe @hide
103493      */
103494     /**
103495      * @cfg {RegExp} maskRe @hide
103496      */
103497
103498     /**
103499      * @cfg {Boolean} allowDecimals
103500      * False to disallow decimal values
103501      */
103502     allowDecimals : true,
103503
103504     /**
103505      * @cfg {String} decimalSeparator
103506      * Character(s) to allow as the decimal separator
103507      */
103508     decimalSeparator : '.',
103509
103510     /**
103511      * @cfg {Number} decimalPrecision
103512      * The maximum precision to display after the decimal separator
103513      */
103514     decimalPrecision : 2,
103515
103516     /**
103517      * @cfg {Number} minValue
103518      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
103519      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
103520      */
103521     minValue: Number.NEGATIVE_INFINITY,
103522
103523     /**
103524      * @cfg {Number} maxValue
103525      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
103526      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
103527      */
103528     maxValue: Number.MAX_VALUE,
103529
103530     /**
103531      * @cfg {Number} step
103532      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
103533      * the spinner.
103534      */
103535     step: 1,
103536
103537     /**
103538      * @cfg {String} minText
103539      * Error text to display if the minimum value validation fails.
103540      */
103541     minText : 'The minimum value for this field is {0}',
103542
103543     /**
103544      * @cfg {String} maxText
103545      * Error text to display if the maximum value validation fails.
103546      */
103547     maxText : 'The maximum value for this field is {0}',
103548
103549     /**
103550      * @cfg {String} nanText
103551      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
103552      * '.' or '-' is left in the field with no number.
103553      */
103554     nanText : '{0} is not a valid number',
103555
103556     /**
103557      * @cfg {String} negativeText
103558      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
103559      * {@link #minText} in that circumstance only.
103560      */
103561     negativeText : 'The value cannot be negative',
103562
103563     /**
103564      * @cfg {String} baseChars
103565      * The base set of characters to evaluate as valid numbers.
103566      */
103567     baseChars : '0123456789',
103568
103569     /**
103570      * @cfg {Boolean} autoStripChars
103571      * True to automatically strip not allowed characters from the field.
103572      */
103573     autoStripChars: false,
103574
103575     initComponent: function() {
103576         var me = this,
103577             allowed;
103578
103579         me.callParent();
103580
103581         me.setMinValue(me.minValue);
103582         me.setMaxValue(me.maxValue);
103583
103584         // Build regexes for masking and stripping based on the configured options
103585         if (me.disableKeyFilter !== true) {
103586             allowed = me.baseChars + '';
103587             if (me.allowDecimals) {
103588                 allowed += me.decimalSeparator;
103589             }
103590             if (me.minValue < 0) {
103591                 allowed += '-';
103592             }
103593             allowed = Ext.String.escapeRegex(allowed);
103594             me.maskRe = new RegExp('[' + allowed + ']');
103595             if (me.autoStripChars) {
103596                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
103597             }
103598         }
103599     },
103600
103601     /**
103602      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
103603      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
103604      * that the value is a number, and that it is within the configured min and max values.
103605      * @param {Object} [value] The value to get errors for (defaults to the current field value)
103606      * @return {String[]} All validation errors for this field
103607      */
103608     getErrors: function(value) {
103609         var me = this,
103610             errors = me.callParent(arguments),
103611             format = Ext.String.format,
103612             num;
103613
103614         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
103615
103616         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
103617              return errors;
103618         }
103619
103620         value = String(value).replace(me.decimalSeparator, '.');
103621
103622         if(isNaN(value)){
103623             errors.push(format(me.nanText, value));
103624         }
103625
103626         num = me.parseValue(value);
103627
103628         if (me.minValue === 0 && num < 0) {
103629             errors.push(this.negativeText);
103630         }
103631         else if (num < me.minValue) {
103632             errors.push(format(me.minText, me.minValue));
103633         }
103634
103635         if (num > me.maxValue) {
103636             errors.push(format(me.maxText, me.maxValue));
103637         }
103638
103639
103640         return errors;
103641     },
103642
103643     rawToValue: function(rawValue) {
103644         var value = this.fixPrecision(this.parseValue(rawValue));
103645         if (value === null) {
103646             value = rawValue || null;
103647         }
103648         return  value;
103649     },
103650
103651     valueToRaw: function(value) {
103652         var me = this,
103653             decimalSeparator = me.decimalSeparator;
103654         value = me.parseValue(value);
103655         value = me.fixPrecision(value);
103656         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
103657         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
103658         return value;
103659     },
103660
103661     onChange: function() {
103662         var me = this,
103663             value = me.getValue(),
103664             valueIsNull = value === null;
103665
103666         me.callParent(arguments);
103667
103668         // Update the spinner buttons
103669         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
103670         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
103671     },
103672
103673     /**
103674      * Replaces any existing {@link #minValue} with the new value.
103675      * @param {Number} value The minimum value
103676      */
103677     setMinValue : function(value) {
103678         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
103679     },
103680
103681     /**
103682      * Replaces any existing {@link #maxValue} with the new value.
103683      * @param {Number} value The maximum value
103684      */
103685     setMaxValue: function(value) {
103686         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
103687     },
103688
103689     // private
103690     parseValue : function(value) {
103691         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
103692         return isNaN(value) ? null : value;
103693     },
103694
103695     /**
103696      * @private
103697      */
103698     fixPrecision : function(value) {
103699         var me = this,
103700             nan = isNaN(value),
103701             precision = me.decimalPrecision;
103702
103703         if (nan || !value) {
103704             return nan ? '' : value;
103705         } else if (!me.allowDecimals || precision <= 0) {
103706             precision = 0;
103707         }
103708
103709         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
103710     },
103711
103712     beforeBlur : function() {
103713         var me = this,
103714             v = me.parseValue(me.getRawValue());
103715
103716         if (!Ext.isEmpty(v)) {
103717             me.setValue(v);
103718         }
103719     },
103720
103721     onSpinUp: function() {
103722         var me = this;
103723         if (!me.readOnly) {
103724             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
103725         }
103726     },
103727
103728     onSpinDown: function() {
103729         var me = this;
103730         if (!me.readOnly) {
103731             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
103732         }
103733     }
103734 });
103735
103736 /**
103737  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
103738  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
103739  * available screen area, vertical scrollbars will be added.
103740  *
103741  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
103742  * which the server needs to interpret and then respond with the appropriate data.
103743  *
103744  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
103745  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
103746  * parameters used for paging criteria.
103747  *
103748  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
103749  *
103750  * Paging Toolbar is typically used as one of the Grid's toolbars:
103751  *
103752  *     @example
103753  *     var itemsPerPage = 2;   // set the number of items you want per page
103754  *
103755  *     var store = Ext.create('Ext.data.Store', {
103756  *         id:'simpsonsStore',
103757  *         autoLoad: false,
103758  *         fields:['name', 'email', 'phone'],
103759  *         pageSize: itemsPerPage, // items per page
103760  *         proxy: {
103761  *             type: 'ajax',
103762  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
103763  *             reader: {
103764  *                 type: 'json',
103765  *                 root: 'items',
103766  *                 totalProperty: 'total'
103767  *             }
103768  *         }
103769  *     });
103770  *
103771  *     // specify segment of data you want to load using params
103772  *     store.load({
103773  *         params:{
103774  *             start:0,
103775  *             limit: itemsPerPage
103776  *         }
103777  *     });
103778  *
103779  *     Ext.create('Ext.grid.Panel', {
103780  *         title: 'Simpsons',
103781  *         store: store,
103782  *         columns: [
103783  *             { header: 'Name',  dataIndex: 'name' },
103784  *             { header: 'Email', dataIndex: 'email', flex: 1 },
103785  *             { header: 'Phone', dataIndex: 'phone' }
103786  *         ],
103787  *         width: 400,
103788  *         height: 125,
103789  *         dockedItems: [{
103790  *             xtype: 'pagingtoolbar',
103791  *             store: store,   // same store GridPanel is using
103792  *             dock: 'bottom',
103793  *             displayInfo: true
103794  *         }],
103795  *         renderTo: Ext.getBody()
103796  *     });
103797  *
103798  * To use paging, pass the paging requirements to the server when the store is first loaded.
103799  *
103800  *     store.load({
103801  *         params: {
103802  *             // specify params for the first page load if using paging
103803  *             start: 0,
103804  *             limit: myPageSize,
103805  *             // other params
103806  *             foo:   'bar'
103807  *         }
103808  *     });
103809  *
103810  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
103811  *
103812  *     var myStore = Ext.create('Ext.data.Store', {
103813  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
103814  *         ...
103815  *     });
103816  *
103817  * The packet sent back from the server would have this form:
103818  *
103819  *     {
103820  *         "success": true,
103821  *         "results": 2000,
103822  *         "rows": [ // ***Note:** this must be an Array
103823  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
103824  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
103825  *             ...
103826  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
103827  *         ]
103828  *     }
103829  *
103830  * ## Paging with Local Data
103831  *
103832  * Paging can also be accomplished with local data using extensions:
103833  *
103834  *   - [Ext.ux.data.PagingStore][1]
103835  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
103836  *
103837  *    [1]: http://sencha.com/forum/showthread.php?t=71532
103838  */
103839 Ext.define('Ext.toolbar.Paging', {
103840     extend: 'Ext.toolbar.Toolbar',
103841     alias: 'widget.pagingtoolbar',
103842     alternateClassName: 'Ext.PagingToolbar',
103843     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
103844     /**
103845      * @cfg {Ext.data.Store} store (required)
103846      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
103847      */
103848
103849     /**
103850      * @cfg {Boolean} displayInfo
103851      * true to display the displayMsg
103852      */
103853     displayInfo: false,
103854
103855     /**
103856      * @cfg {Boolean} prependButtons
103857      * true to insert any configured items _before_ the paging buttons.
103858      */
103859     prependButtons: false,
103860
103861     /**
103862      * @cfg {String} displayMsg
103863      * The paging status message to display. Note that this string is
103864      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
103865      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
103866      */
103867     displayMsg : 'Displaying {0} - {1} of {2}',
103868
103869     /**
103870      * @cfg {String} emptyMsg
103871      * The message to display when no records are found.
103872      */
103873     emptyMsg : 'No data to display',
103874
103875     /**
103876      * @cfg {String} beforePageText
103877      * The text displayed before the input item.
103878      */
103879     beforePageText : 'Page',
103880
103881     /**
103882      * @cfg {String} afterPageText
103883      * Customizable piece of the default paging text. Note that this string is formatted using
103884      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
103885      * string if showing the total page count is desired.
103886      */
103887     afterPageText : 'of {0}',
103888
103889     /**
103890      * @cfg {String} firstText
103891      * The quicktip text displayed for the first page button.
103892      * **Note**: quick tips must be initialized for the quicktip to show.
103893      */
103894     firstText : 'First Page',
103895
103896     /**
103897      * @cfg {String} prevText
103898      * The quicktip text displayed for the previous page button.
103899      * **Note**: quick tips must be initialized for the quicktip to show.
103900      */
103901     prevText : 'Previous Page',
103902
103903     /**
103904      * @cfg {String} nextText
103905      * The quicktip text displayed for the next page button.
103906      * **Note**: quick tips must be initialized for the quicktip to show.
103907      */
103908     nextText : 'Next Page',
103909
103910     /**
103911      * @cfg {String} lastText
103912      * The quicktip text displayed for the last page button.
103913      * **Note**: quick tips must be initialized for the quicktip to show.
103914      */
103915     lastText : 'Last Page',
103916
103917     /**
103918      * @cfg {String} refreshText
103919      * The quicktip text displayed for the Refresh button.
103920      * **Note**: quick tips must be initialized for the quicktip to show.
103921      */
103922     refreshText : 'Refresh',
103923
103924     /**
103925      * @cfg {Number} inputItemWidth
103926      * The width in pixels of the input field used to display and change the current page number.
103927      */
103928     inputItemWidth : 30,
103929
103930     /**
103931      * Gets the standard paging items in the toolbar
103932      * @private
103933      */
103934     getPagingItems: function() {
103935         var me = this;
103936
103937         return [{
103938             itemId: 'first',
103939             tooltip: me.firstText,
103940             overflowText: me.firstText,
103941             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
103942             disabled: true,
103943             handler: me.moveFirst,
103944             scope: me
103945         },{
103946             itemId: 'prev',
103947             tooltip: me.prevText,
103948             overflowText: me.prevText,
103949             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
103950             disabled: true,
103951             handler: me.movePrevious,
103952             scope: me
103953         },
103954         '-',
103955         me.beforePageText,
103956         {
103957             xtype: 'numberfield',
103958             itemId: 'inputItem',
103959             name: 'inputItem',
103960             cls: Ext.baseCSSPrefix + 'tbar-page-number',
103961             allowDecimals: false,
103962             minValue: 1,
103963             hideTrigger: true,
103964             enableKeyEvents: true,
103965             selectOnFocus: true,
103966             submitValue: false,
103967             width: me.inputItemWidth,
103968             margins: '-1 2 3 2',
103969             listeners: {
103970                 scope: me,
103971                 keydown: me.onPagingKeyDown,
103972                 blur: me.onPagingBlur
103973             }
103974         },{
103975             xtype: 'tbtext',
103976             itemId: 'afterTextItem',
103977             text: Ext.String.format(me.afterPageText, 1)
103978         },
103979         '-',
103980         {
103981             itemId: 'next',
103982             tooltip: me.nextText,
103983             overflowText: me.nextText,
103984             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
103985             disabled: true,
103986             handler: me.moveNext,
103987             scope: me
103988         },{
103989             itemId: 'last',
103990             tooltip: me.lastText,
103991             overflowText: me.lastText,
103992             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
103993             disabled: true,
103994             handler: me.moveLast,
103995             scope: me
103996         },
103997         '-',
103998         {
103999             itemId: 'refresh',
104000             tooltip: me.refreshText,
104001             overflowText: me.refreshText,
104002             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
104003             handler: me.doRefresh,
104004             scope: me
104005         }];
104006     },
104007
104008     initComponent : function(){
104009         var me = this,
104010             pagingItems = me.getPagingItems(),
104011             userItems   = me.items || me.buttons || [];
104012
104013         if (me.prependButtons) {
104014             me.items = userItems.concat(pagingItems);
104015         } else {
104016             me.items = pagingItems.concat(userItems);
104017         }
104018         delete me.buttons;
104019
104020         if (me.displayInfo) {
104021             me.items.push('->');
104022             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
104023         }
104024
104025         me.callParent();
104026
104027         me.addEvents(
104028             /**
104029              * @event change
104030              * Fires after the active page has been changed.
104031              * @param {Ext.toolbar.Paging} this
104032              * @param {Object} pageData An object that has these properties:
104033              *
104034              * - `total` : Number
104035              *
104036              *   The total number of records in the dataset as returned by the server
104037              *
104038              * - `currentPage` : Number
104039              *
104040              *   The current page number
104041              *
104042              * - `pageCount` : Number
104043              *
104044              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
104045              *   server and the current {@link Ext.data.Store#pageSize pageSize})
104046              *
104047              * - `toRecord` : Number
104048              *
104049              *   The starting record index for the current page
104050              *
104051              * - `fromRecord` : Number
104052              *
104053              *   The ending record index for the current page
104054              */
104055             'change',
104056
104057             /**
104058              * @event beforechange
104059              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
104060              * @param {Ext.toolbar.Paging} this
104061              * @param {Number} page The page number that will be loaded on change
104062              */
104063             'beforechange'
104064         );
104065         me.on('afterlayout', me.onLoad, me, {single: true});
104066
104067         me.bindStore(me.store || 'ext-empty-store', true);
104068     },
104069     // private
104070     updateInfo : function(){
104071         var me = this,
104072             displayItem = me.child('#displayItem'),
104073             store = me.store,
104074             pageData = me.getPageData(),
104075             count, msg;
104076
104077         if (displayItem) {
104078             count = store.getCount();
104079             if (count === 0) {
104080                 msg = me.emptyMsg;
104081             } else {
104082                 msg = Ext.String.format(
104083                     me.displayMsg,
104084                     pageData.fromRecord,
104085                     pageData.toRecord,
104086                     pageData.total
104087                 );
104088             }
104089             displayItem.setText(msg);
104090             me.doComponentLayout();
104091         }
104092     },
104093
104094     // private
104095     onLoad : function(){
104096         var me = this,
104097             pageData,
104098             currPage,
104099             pageCount,
104100             afterText;
104101
104102         if (!me.rendered) {
104103             return;
104104         }
104105
104106         pageData = me.getPageData();
104107         currPage = pageData.currentPage;
104108         pageCount = pageData.pageCount;
104109         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
104110
104111         me.child('#afterTextItem').setText(afterText);
104112         me.child('#inputItem').setValue(currPage);
104113         me.child('#first').setDisabled(currPage === 1);
104114         me.child('#prev').setDisabled(currPage === 1);
104115         me.child('#next').setDisabled(currPage === pageCount);
104116         me.child('#last').setDisabled(currPage === pageCount);
104117         me.child('#refresh').enable();
104118         me.updateInfo();
104119         me.fireEvent('change', me, pageData);
104120     },
104121
104122     // private
104123     getPageData : function(){
104124         var store = this.store,
104125             totalCount = store.getTotalCount();
104126
104127         return {
104128             total : totalCount,
104129             currentPage : store.currentPage,
104130             pageCount: Math.ceil(totalCount / store.pageSize),
104131             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104132             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
104133
104134         };
104135     },
104136
104137     // private
104138     onLoadError : function(){
104139         if (!this.rendered) {
104140             return;
104141         }
104142         this.child('#refresh').enable();
104143     },
104144
104145     // private
104146     readPageFromInput : function(pageData){
104147         var v = this.child('#inputItem').getValue(),
104148             pageNum = parseInt(v, 10);
104149
104150         if (!v || isNaN(pageNum)) {
104151             this.child('#inputItem').setValue(pageData.currentPage);
104152             return false;
104153         }
104154         return pageNum;
104155     },
104156
104157     onPagingFocus : function(){
104158         this.child('#inputItem').select();
104159     },
104160
104161     //private
104162     onPagingBlur : function(e){
104163         var curPage = this.getPageData().currentPage;
104164         this.child('#inputItem').setValue(curPage);
104165     },
104166
104167     // private
104168     onPagingKeyDown : function(field, e){
104169         var me = this,
104170             k = e.getKey(),
104171             pageData = me.getPageData(),
104172             increment = e.shiftKey ? 10 : 1,
104173             pageNum;
104174
104175         if (k == e.RETURN) {
104176             e.stopEvent();
104177             pageNum = me.readPageFromInput(pageData);
104178             if (pageNum !== false) {
104179                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
104180                 if(me.fireEvent('beforechange', me, pageNum) !== false){
104181                     me.store.loadPage(pageNum);
104182                 }
104183             }
104184         } else if (k == e.HOME || k == e.END) {
104185             e.stopEvent();
104186             pageNum = k == e.HOME ? 1 : pageData.pageCount;
104187             field.setValue(pageNum);
104188         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
104189             e.stopEvent();
104190             pageNum = me.readPageFromInput(pageData);
104191             if (pageNum) {
104192                 if (k == e.DOWN || k == e.PAGEDOWN) {
104193                     increment *= -1;
104194                 }
104195                 pageNum += increment;
104196                 if (pageNum >= 1 && pageNum <= pageData.pages) {
104197                     field.setValue(pageNum);
104198                 }
104199             }
104200         }
104201     },
104202
104203     // private
104204     beforeLoad : function(){
104205         if(this.rendered && this.refresh){
104206             this.refresh.disable();
104207         }
104208     },
104209
104210     // private
104211     doLoad : function(start){
104212         if(this.fireEvent('beforechange', this, o) !== false){
104213             this.store.load();
104214         }
104215     },
104216
104217     /**
104218      * Move to the first page, has the same effect as clicking the 'first' button.
104219      */
104220     moveFirst : function(){
104221         if (this.fireEvent('beforechange', this, 1) !== false){
104222             this.store.loadPage(1);
104223         }
104224     },
104225
104226     /**
104227      * Move to the previous page, has the same effect as clicking the 'previous' button.
104228      */
104229     movePrevious : function(){
104230         var me = this,
104231             prev = me.store.currentPage - 1;
104232
104233         if (prev > 0) {
104234             if (me.fireEvent('beforechange', me, prev) !== false) {
104235                 me.store.previousPage();
104236             }
104237         }
104238     },
104239
104240     /**
104241      * Move to the next page, has the same effect as clicking the 'next' button.
104242      */
104243     moveNext : function(){
104244         var me = this,
104245             total = me.getPageData().pageCount,
104246             next = me.store.currentPage + 1;
104247
104248         if (next <= total) {
104249             if (me.fireEvent('beforechange', me, next) !== false) {
104250                 me.store.nextPage();
104251             }
104252         }
104253     },
104254
104255     /**
104256      * Move to the last page, has the same effect as clicking the 'last' button.
104257      */
104258     moveLast : function(){
104259         var me = this,
104260             last = me.getPageData().pageCount;
104261
104262         if (me.fireEvent('beforechange', me, last) !== false) {
104263             me.store.loadPage(last);
104264         }
104265     },
104266
104267     /**
104268      * Refresh the current page, has the same effect as clicking the 'refresh' button.
104269      */
104270     doRefresh : function(){
104271         var me = this,
104272             current = me.store.currentPage;
104273
104274         if (me.fireEvent('beforechange', me, current) !== false) {
104275             me.store.loadPage(current);
104276         }
104277     },
104278
104279     /**
104280      * Binds the paging toolbar to the specified {@link Ext.data.Store}
104281      * @param {Ext.data.Store} store The store to bind to this toolbar
104282      * @param {Boolean} initial (Optional) true to not remove listeners
104283      */
104284     bindStore : function(store, initial){
104285         var me = this;
104286
104287         if (!initial && me.store) {
104288             if(store !== me.store && me.store.autoDestroy){
104289                 me.store.destroyStore();
104290             }else{
104291                 me.store.un('beforeload', me.beforeLoad, me);
104292                 me.store.un('load', me.onLoad, me);
104293                 me.store.un('exception', me.onLoadError, me);
104294             }
104295             if(!store){
104296                 me.store = null;
104297             }
104298         }
104299         if (store) {
104300             store = Ext.data.StoreManager.lookup(store);
104301             store.on({
104302                 scope: me,
104303                 beforeload: me.beforeLoad,
104304                 load: me.onLoad,
104305                 exception: me.onLoadError
104306             });
104307         }
104308         me.store = store;
104309     },
104310
104311     /**
104312      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
104313      * @param {Ext.data.Store} store The data store to unbind
104314      */
104315     unbind : function(store){
104316         this.bindStore(null);
104317     },
104318
104319     /**
104320      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
104321      * @param {Ext.data.Store} store The data store to bind
104322      */
104323     bind : function(store){
104324         this.bindStore(store);
104325     },
104326
104327     // private
104328     onDestroy : function(){
104329         this.bindStore(null);
104330         this.callParent();
104331     }
104332 });
104333
104334 /**
104335  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
104336  */
104337 Ext.define('Ext.view.BoundList', {
104338     extend: 'Ext.view.View',
104339     alias: 'widget.boundlist',
104340     alternateClassName: 'Ext.BoundList',
104341     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
104342
104343     /**
104344      * @cfg {Number} pageSize
104345      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
104346      * queries will execute with page {@link Ext.data.Operation#start start} and
104347      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
104348      */
104349     pageSize: 0,
104350
104351     /**
104352      * @property {Ext.toolbar.Paging} pagingToolbar
104353      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
104354      * than zero and the BoundList has been rendered.
104355      */
104356
104357     // private overrides
104358     autoScroll: true,
104359     baseCls: Ext.baseCSSPrefix + 'boundlist',
104360     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
104361     listItemCls: '',
104362     shadow: false,
104363     trackOver: true,
104364     refreshed: 0,
104365
104366     ariaRole: 'listbox',
104367
104368     componentLayout: 'boundlist',
104369
104370     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
104371
104372     initComponent: function() {
104373         var me = this,
104374             baseCls = me.baseCls,
104375             itemCls = me.itemCls;
104376             
104377         me.selectedItemCls = baseCls + '-selected';
104378         me.overItemCls = baseCls + '-item-over';
104379         me.itemSelector = "." + itemCls;
104380
104381         if (me.floating) {
104382             me.addCls(baseCls + '-floating');
104383         }
104384
104385         if (!me.tpl) {
104386             // should be setting aria-posinset based on entire set of data
104387             // not filtered set
104388             me.tpl = Ext.create('Ext.XTemplate',
104389                 '<ul><tpl for=".">',
104390                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
104391                 '</tpl></ul>'
104392             );
104393         } else if (Ext.isString(me.tpl)) {
104394             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
104395         }
104396
104397         if (me.pageSize) {
104398             me.pagingToolbar = me.createPagingToolbar();
104399         }
104400
104401         me.callParent();
104402
104403         me.addChildEls('listEl');
104404     },
104405
104406     createPagingToolbar: function() {
104407         return Ext.widget('pagingtoolbar', {
104408             pageSize: this.pageSize,
104409             store: this.store,
104410             border: false
104411         });
104412     },
104413
104414     onRender: function() {
104415         var me = this,
104416             toolbar = me.pagingToolbar;
104417         me.callParent(arguments);
104418         if (toolbar) {
104419             toolbar.render(me.el);
104420         }
104421     },
104422
104423     bindStore : function(store, initial) {
104424         var me = this,
104425             toolbar = me.pagingToolbar;
104426         me.callParent(arguments);
104427         if (toolbar) {
104428             toolbar.bindStore(store, initial);
104429         }
104430     },
104431
104432     getTargetEl: function() {
104433         return this.listEl || this.el;
104434     },
104435
104436     getInnerTpl: function(displayField) {
104437         return '{' + displayField + '}';
104438     },
104439
104440     refresh: function() {
104441         var me = this;
104442         me.callParent();
104443         if (me.isVisible()) {
104444             me.refreshed++;
104445             me.doComponentLayout();
104446             me.refreshed--;
104447         }
104448     },
104449
104450     initAria: function() {
104451         this.callParent();
104452
104453         var selModel = this.getSelectionModel(),
104454             mode     = selModel.getSelectionMode(),
104455             actionEl = this.getActionEl();
104456
104457         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
104458         if (mode !== 'SINGLE') {
104459             actionEl.dom.setAttribute('aria-multiselectable', true);
104460         }
104461     },
104462
104463     onDestroy: function() {
104464         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
104465         this.callParent();
104466     }
104467 });
104468
104469 /**
104470  * @class Ext.view.BoundListKeyNav
104471  * @extends Ext.util.KeyNav
104472  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
104473  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
104474  * through the list. The enter key invokes the selection model's select action using the highlighted item.
104475  */
104476 Ext.define('Ext.view.BoundListKeyNav', {
104477     extend: 'Ext.util.KeyNav',
104478     requires: 'Ext.view.BoundList',
104479
104480     /**
104481      * @cfg {Ext.view.BoundList} boundList (required)
104482      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
104483      */
104484
104485     constructor: function(el, config) {
104486         var me = this;
104487         me.boundList = config.boundList;
104488         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
104489     },
104490
104491     defaultHandlers: {
104492         up: function() {
104493             var me = this,
104494                 boundList = me.boundList,
104495                 allItems = boundList.all,
104496                 oldItem = boundList.highlightedItem,
104497                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104498                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
104499             me.highlightAt(newItemIdx);
104500         },
104501
104502         down: function() {
104503             var me = this,
104504                 boundList = me.boundList,
104505                 allItems = boundList.all,
104506                 oldItem = boundList.highlightedItem,
104507                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104508                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
104509             me.highlightAt(newItemIdx);
104510         },
104511
104512         pageup: function() {
104513             //TODO
104514         },
104515
104516         pagedown: function() {
104517             //TODO
104518         },
104519
104520         home: function() {
104521             this.highlightAt(0);
104522         },
104523
104524         end: function() {
104525             var me = this;
104526             me.highlightAt(me.boundList.all.getCount() - 1);
104527         },
104528
104529         enter: function(e) {
104530             this.selectHighlighted(e);
104531         }
104532     },
104533
104534     /**
104535      * Highlights the item at the given index.
104536      * @param {Number} index
104537      */
104538     highlightAt: function(index) {
104539         var boundList = this.boundList,
104540             item = boundList.all.item(index);
104541         if (item) {
104542             item = item.dom;
104543             boundList.highlightItem(item);
104544             boundList.getTargetEl().scrollChildIntoView(item, false);
104545         }
104546     },
104547
104548     /**
104549      * Triggers selection of the currently highlighted item according to the behavior of
104550      * the configured SelectionModel.
104551      */
104552     selectHighlighted: function(e) {
104553         var me = this,
104554             boundList = me.boundList,
104555             highlighted = boundList.highlightedItem,
104556             selModel = boundList.getSelectionModel();
104557         if (highlighted) {
104558             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
104559         }
104560     }
104561
104562 });
104563 /**
104564  * @docauthor Jason Johnston <jason@sencha.com>
104565  *
104566  * A combobox control with support for autocomplete, remote loading, and many other features.
104567  *
104568  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
104569  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
104570  * list. The user can input any value by default, even if it does not appear in the selection list;
104571  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
104572  *
104573  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
104574  * stores. The data items in the store are mapped to each option's displayed text and backing value via
104575  * the {@link #valueField} and {@link #displayField} configurations, respectively.
104576  *
104577  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
104578  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
104579  *
104580  * # Example usage:
104581  *
104582  *     @example
104583  *     // The data store containing the list of states
104584  *     var states = Ext.create('Ext.data.Store', {
104585  *         fields: ['abbr', 'name'],
104586  *         data : [
104587  *             {"abbr":"AL", "name":"Alabama"},
104588  *             {"abbr":"AK", "name":"Alaska"},
104589  *             {"abbr":"AZ", "name":"Arizona"}
104590  *             //...
104591  *         ]
104592  *     });
104593  *
104594  *     // Create the combo box, attached to the states data store
104595  *     Ext.create('Ext.form.ComboBox', {
104596  *         fieldLabel: 'Choose State',
104597  *         store: states,
104598  *         queryMode: 'local',
104599  *         displayField: 'name',
104600  *         valueField: 'abbr',
104601  *         renderTo: Ext.getBody()
104602  *     });
104603  *
104604  * # Events
104605  *
104606  * To do something when something in ComboBox is selected, configure the select event:
104607  *
104608  *     var cb = Ext.create('Ext.form.ComboBox', {
104609  *         // all of your config options
104610  *         listeners:{
104611  *              scope: yourScope,
104612  *              'select': yourFunction
104613  *         }
104614  *     });
104615  *
104616  *     // Alternatively, you can assign events after the object is created:
104617  *     var cb = new Ext.form.field.ComboBox(yourOptions);
104618  *     cb.on('select', yourFunction, yourScope);
104619  *
104620  * # Multiple Selection
104621  *
104622  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
104623  * {@link #multiSelect} config to `true`.
104624  */
104625 Ext.define('Ext.form.field.ComboBox', {
104626     extend:'Ext.form.field.Picker',
104627     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
104628     alternateClassName: 'Ext.form.ComboBox',
104629     alias: ['widget.combobox', 'widget.combo'],
104630
104631     /**
104632      * @cfg {String} [triggerCls='x-form-arrow-trigger']
104633      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
104634      * by default and `triggerCls` will be **appended** if specified.
104635      */
104636     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
104637
104638     /**
104639      * @private
104640      * @cfg {String}
104641      * CSS class used to find the {@link #hiddenDataEl}
104642      */
104643     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
104644
104645     /**
104646      * @override
104647      */
104648     fieldSubTpl: [
104649         '<div class="{hiddenDataCls}" role="presentation"></div>',
104650         '<input id="{id}" type="{type}" ',
104651             '<tpl if="size">size="{size}" </tpl>',
104652             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
104653             'class="{fieldCls} {typeCls}" autocomplete="off" />',
104654         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
104655             '{triggerEl}',
104656             '<div class="{clearCls}" role="presentation"></div>',
104657         '</div>',
104658         {
104659             compiled: true,
104660             disableFormats: true
104661         }
104662     ],
104663
104664     getSubTplData: function(){
104665         var me = this;
104666         Ext.applyIf(me.subTplData, {
104667             hiddenDataCls: me.hiddenDataCls
104668         });
104669         return me.callParent(arguments);
104670     },
104671
104672     afterRender: function(){
104673         var me = this;
104674         me.callParent(arguments);
104675         me.setHiddenValue(me.value);
104676     },
104677
104678     /**
104679      * @cfg {Ext.data.Store/Array} store
104680      * The data source to which this combo is bound. Acceptable values for this property are:
104681      *
104682      *   - **any {@link Ext.data.Store Store} subclass**
104683      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
104684      *     {@link Ext.data.Field#name field names} to work with all data components.
104685      *
104686      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
104687      *
104688      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
104689      *       {@link #valueField} and {@link #displayField})
104690      *
104691      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
104692      *
104693      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
104694      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
104695      *
104696      * See also {@link #queryMode}.
104697      */
104698
104699     /**
104700      * @cfg {Boolean} multiSelect
104701      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
104702      * items from the dropdown list. The combo's text field will show all selected values separated by the
104703      * {@link #delimiter}.
104704      */
104705     multiSelect: false,
104706
104707     /**
104708      * @cfg {String} delimiter
104709      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
104710      * `{@link #multiSelect} = true`.
104711      */
104712     delimiter: ', ',
104713
104714     /**
104715      * @cfg {String} displayField
104716      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
104717      *
104718      * See also `{@link #valueField}`.
104719      */
104720     displayField: 'text',
104721
104722     /**
104723      * @cfg {String} valueField (required)
104724      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
104725      * the value of the {@link #displayField} config).
104726      *
104727      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
104728      * `{@link #displayField}`.
104729      */
104730
104731     /**
104732      * @cfg {String} triggerAction
104733      * The action to execute when the trigger is clicked.
104734      *
104735      *   - **`'all'`** :
104736      *
104737      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
104738      *
104739      *   - **`'query'`** :
104740      *
104741      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
104742      *
104743      * See also `{@link #queryParam}`.
104744      */
104745     triggerAction: 'all',
104746
104747     /**
104748      * @cfg {String} allQuery
104749      * The text query to send to the server to return all records for the list with no filtering
104750      */
104751     allQuery: '',
104752
104753     /**
104754      * @cfg {String} queryParam
104755      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
104756      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
104757      */
104758     queryParam: 'query',
104759
104760     /**
104761      * @cfg {String} queryMode
104762      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
104763      *
104764      *   - **`'remote'`** :
104765      *
104766      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
104767      *
104768      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
104769      *     Ext.data.Store#load load}ed.
104770      *
104771      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
104772      *     string is `query`, but this can be configured using the {@link #queryParam} config.
104773      *
104774      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
104775      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
104776      *     request which allows the server to further refine the returned dataset.
104777      *
104778      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
104779      *     autocomplete.
104780      *
104781      *   - **`'local'`** :
104782      *
104783      *     ComboBox loads local data
104784      *
104785      *         var combo = new Ext.form.field.ComboBox({
104786      *             renderTo: document.body,
104787      *             queryMode: 'local',
104788      *             store: new Ext.data.ArrayStore({
104789      *                 id: 0,
104790      *                 fields: [
104791      *                     'myId',  // numeric value is the key
104792      *                     'displayText'
104793      *                 ],
104794      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
104795      *             }),
104796      *             valueField: 'myId',
104797      *             displayField: 'displayText',
104798      *             triggerAction: 'all'
104799      *         });
104800      */
104801     queryMode: 'remote',
104802
104803     queryCaching: true,
104804
104805     /**
104806      * @cfg {Number} pageSize
104807      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
104808      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
104809      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
104810      */
104811     pageSize: 0,
104812
104813     /**
104814      * @cfg {Number} queryDelay
104815      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
104816      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
104817      */
104818
104819     /**
104820      * @cfg {Number} minChars
104821      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
104822      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
104823      * `{@link Ext.form.field.Trigger#editable editable} = false`).
104824      */
104825
104826     /**
104827      * @cfg {Boolean} autoSelect
104828      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
104829      * opened. A false value would cause nothing in the list to be highlighted automatically, so
104830      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
104831      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
104832      */
104833     autoSelect: true,
104834
104835     /**
104836      * @cfg {Boolean} typeAhead
104837      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
104838      * ({@link #typeAheadDelay}) if it matches a known value.
104839      */
104840     typeAhead: false,
104841
104842     /**
104843      * @cfg {Number} typeAheadDelay
104844      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
104845      */
104846     typeAheadDelay: 250,
104847
104848     /**
104849      * @cfg {Boolean} selectOnTab
104850      * Whether the Tab key should select the currently highlighted item.
104851      */
104852     selectOnTab: true,
104853
104854     /**
104855      * @cfg {Boolean} forceSelection
104856      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
104857      * arbitrary text into the field.
104858      */
104859     forceSelection: false,
104860
104861     /**
104862      * @cfg {String} valueNotFoundText
104863      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
104864      * be displayed as the field text if defined. If this default text is used, it means there
104865      * is no value set and no validation will occur on this field.
104866      */
104867
104868     /**
104869      * @property {String} lastQuery
104870      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
104871      *
104872      *     var combo = new Ext.form.field.ComboBox({
104873      *         ...
104874      *         queryMode: 'remote',
104875      *         listeners: {
104876      *             // delete the previous query in the beforequery event or set
104877      *             // combo.lastQuery = null (this will reload the store the next time it expands)
104878      *             beforequery: function(qe){
104879      *                 delete qe.combo.lastQuery;
104880      *             }
104881      *         }
104882      *     });
104883      *
104884      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
104885      * combo with `lastQuery=''`. Example use:
104886      *
104887      *     var combo = new Ext.form.field.ComboBox({
104888      *         ...
104889      *         queryMode: 'local',
104890      *         triggerAction: 'all',
104891      *         lastQuery: ''
104892      *     });
104893      */
104894
104895     /**
104896      * @cfg {Object} defaultListConfig
104897      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
104898      */
104899     defaultListConfig: {
104900         emptyText: '',
104901         loadingText: 'Loading...',
104902         loadingHeight: 70,
104903         minWidth: 70,
104904         maxHeight: 300,
104905         shadow: 'sides'
104906     },
104907
104908     /**
104909      * @cfg {String/HTMLElement/Ext.Element} transform
104910      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
104911      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
104912      * will take precedence over this.
104913      */
104914
104915     /**
104916      * @cfg {Object} listConfig
104917      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
104918      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
104919      *
104920      *   - {@link Ext.view.BoundList#cls} - defaults to empty
104921      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
104922      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
104923      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
104924      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
104925      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
104926      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
104927      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
104928      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
104929      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
104930      *     field if {@link #matchFieldWidth} is true)
104931      */
104932
104933     //private
104934     ignoreSelection: 0,
104935
104936     initComponent: function() {
104937         var me = this,
104938             isDefined = Ext.isDefined,
104939             store = me.store,
104940             transform = me.transform,
104941             transformSelect, isLocalMode;
104942
104943         Ext.applyIf(me.renderSelectors, {
104944             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
104945         });
104946         
104947
104948         this.addEvents(
104949             /**
104950              * @event beforequery
104951              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
104952              * property to true.
104953              *
104954              * @param {Object} queryEvent An object that has these properties:
104955              *
104956              *   - `combo` : Ext.form.field.ComboBox
104957              *
104958              *     This combo box
104959              *
104960              *   - `query` : String
104961              *
104962              *     The query string
104963              *
104964              *   - `forceAll` : Boolean
104965              *
104966              *     True to force "all" query
104967              *
104968              *   - `cancel` : Boolean
104969              *
104970              *     Set to true to cancel the query
104971              */
104972             'beforequery',
104973
104974             /**
104975              * @event select
104976              * Fires when at least one list item is selected.
104977              * @param {Ext.form.field.ComboBox} combo This combo box
104978              * @param {Array} records The selected records
104979              */
104980             'select',
104981
104982             /**
104983              * @event beforeselect
104984              * Fires before the selected item is added to the collection
104985              * @param {Ext.form.field.ComboBox} combo This combo box
104986              * @param {Ext.data.Record} record The selected record
104987              * @param {Number} index The index of the selected record
104988              */
104989             'beforeselect',
104990
104991             /**
104992              * @event beforedeselect
104993              * Fires before the deselected item is removed from the collection
104994              * @param {Ext.form.field.ComboBox} combo This combo box
104995              * @param {Ext.data.Record} record The deselected record
104996              * @param {Number} index The index of the deselected record
104997              */
104998             'beforedeselect'
104999         );
105000
105001         // Build store from 'transform' HTML select element's options
105002         if (transform) {
105003             transformSelect = Ext.getDom(transform);
105004             if (transformSelect) {
105005                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
105006                     return [option.value, option.text];
105007                 });
105008                 if (!me.name) {
105009                     me.name = transformSelect.name;
105010                 }
105011                 if (!('value' in me)) {
105012                     me.value = transformSelect.value;
105013                 }
105014             }
105015         }
105016
105017         me.bindStore(store || 'ext-empty-store', true);
105018         store = me.store;
105019         if (store.autoCreated) {
105020             me.queryMode = 'local';
105021             me.valueField = me.displayField = 'field1';
105022             if (!store.expanded) {
105023                 me.displayField = 'field2';
105024             }
105025         }
105026
105027
105028         if (!isDefined(me.valueField)) {
105029             me.valueField = me.displayField;
105030         }
105031
105032         isLocalMode = me.queryMode === 'local';
105033         if (!isDefined(me.queryDelay)) {
105034             me.queryDelay = isLocalMode ? 10 : 500;
105035         }
105036         if (!isDefined(me.minChars)) {
105037             me.minChars = isLocalMode ? 0 : 4;
105038         }
105039
105040         if (!me.displayTpl) {
105041             me.displayTpl = Ext.create('Ext.XTemplate',
105042                 '<tpl for=".">' +
105043                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
105044                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
105045                 '</tpl>'
105046             );
105047         } else if (Ext.isString(me.displayTpl)) {
105048             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
105049         }
105050
105051         me.callParent();
105052
105053         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
105054
105055         // store has already been loaded, setValue
105056         if (me.store.getCount() > 0) {
105057             me.setValue(me.value);
105058         }
105059
105060         // render in place of 'transform' select
105061         if (transformSelect) {
105062             me.render(transformSelect.parentNode, transformSelect);
105063             Ext.removeNode(transformSelect);
105064             delete me.renderTo;
105065         }
105066     },
105067
105068     /**
105069      * Returns the store associated with this ComboBox.
105070      * @return {Ext.data.Store} The store
105071      */
105072     getStore : function(){
105073         return this.store;
105074     },
105075
105076     beforeBlur: function() {
105077         this.doQueryTask.cancel();
105078         this.assertValue();
105079     },
105080
105081     // private
105082     assertValue: function() {
105083         var me = this,
105084             value = me.getRawValue(),
105085             rec;
105086
105087         if (me.forceSelection) {
105088             if (me.multiSelect) {
105089                 // For multiselect, check that the current displayed value matches the current
105090                 // selection, if it does not then revert to the most recent selection.
105091                 if (value !== me.getDisplayValue()) {
105092                     me.setValue(me.lastSelection);
105093                 }
105094             } else {
105095                 // For single-select, match the displayed value to a record and select it,
105096                 // if it does not match a record then revert to the most recent selection.
105097                 rec = me.findRecordByDisplay(value);
105098                 if (rec) {
105099                     me.select(rec);
105100                 } else {
105101                     me.setValue(me.lastSelection);
105102                 }
105103             }
105104         }
105105         me.collapse();
105106     },
105107
105108     onTypeAhead: function() {
105109         var me = this,
105110             displayField = me.displayField,
105111             record = me.store.findRecord(displayField, me.getRawValue()),
105112             boundList = me.getPicker(),
105113             newValue, len, selStart;
105114
105115         if (record) {
105116             newValue = record.get(displayField);
105117             len = newValue.length;
105118             selStart = me.getRawValue().length;
105119
105120             boundList.highlightItem(boundList.getNode(record));
105121
105122             if (selStart !== 0 && selStart !== len) {
105123                 me.setRawValue(newValue);
105124                 me.selectText(selStart, newValue.length);
105125             }
105126         }
105127     },
105128
105129     // invoked when a different store is bound to this combo
105130     // than the original
105131     resetToDefault: function() {
105132
105133     },
105134
105135     bindStore: function(store, initial) {
105136         var me = this,
105137             oldStore = me.store;
105138
105139         // this code directly accesses this.picker, bc invoking getPicker
105140         // would create it when we may be preping to destroy it
105141         if (oldStore && !initial) {
105142             if (oldStore !== store && oldStore.autoDestroy) {
105143                 oldStore.destroyStore();
105144             } else {
105145                 oldStore.un({
105146                     scope: me,
105147                     load: me.onLoad,
105148                     exception: me.collapse
105149                 });
105150             }
105151             if (!store) {
105152                 me.store = null;
105153                 if (me.picker) {
105154                     me.picker.bindStore(null);
105155                 }
105156             }
105157         }
105158         if (store) {
105159             if (!initial) {
105160                 me.resetToDefault();
105161             }
105162
105163             me.store = Ext.data.StoreManager.lookup(store);
105164             me.store.on({
105165                 scope: me,
105166                 load: me.onLoad,
105167                 exception: me.collapse
105168             });
105169
105170             if (me.picker) {
105171                 me.picker.bindStore(store);
105172             }
105173         }
105174     },
105175
105176     onLoad: function() {
105177         var me = this,
105178             value = me.value;
105179
105180         // If performing a remote query upon the raw value...
105181         if (me.rawQuery) {
105182             me.rawQuery = false;
105183             me.syncSelection();
105184             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
105185                 me.doAutoSelect();
105186             }
105187         }
105188         // If store initial load or triggerAction: 'all' trigger click.
105189         else {
105190             // Set the value on load
105191             if (me.value) {
105192                 me.setValue(me.value);
105193             } else {
105194                 // There's no value.
105195                 // Highlight the first item in the list if autoSelect: true
105196                 if (me.store.getCount()) {
105197                     me.doAutoSelect();
105198                 } else {
105199                     me.setValue('');
105200                 }
105201             }
105202         }
105203     },
105204
105205     /**
105206      * @private
105207      * Execute the query with the raw contents within the textfield.
105208      */
105209     doRawQuery: function() {
105210         this.doQuery(this.getRawValue(), false, true);
105211     },
105212
105213     /**
105214      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
105215      * allowing the query action to be canceled if needed.
105216      *
105217      * @param {String} queryString The SQL query to execute
105218      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
105219      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
105220      * previously saved in the current store.
105221      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
105222      * resulting store load to leave the raw value undisturbed.
105223      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
105224      * handler.
105225      */
105226     doQuery: function(queryString, forceAll, rawQuery) {
105227         queryString = queryString || '';
105228
105229         // store in object and pass by reference in 'beforequery'
105230         // so that client code can modify values.
105231         var me = this,
105232             qe = {
105233                 query: queryString,
105234                 forceAll: forceAll,
105235                 combo: me,
105236                 cancel: false
105237             },
105238             store = me.store,
105239             isLocalMode = me.queryMode === 'local';
105240
105241         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
105242             return false;
105243         }
105244
105245         // get back out possibly modified values
105246         queryString = qe.query;
105247         forceAll = qe.forceAll;
105248
105249         // query permitted to run
105250         if (forceAll || (queryString.length >= me.minChars)) {
105251             // expand before starting query so LoadMask can position itself correctly
105252             me.expand();
105253
105254             // make sure they aren't querying the same thing
105255             if (!me.queryCaching || me.lastQuery !== queryString) {
105256                 me.lastQuery = queryString;
105257
105258                 if (isLocalMode) {
105259                     // forceAll means no filtering - show whole dataset.
105260                     if (forceAll) {
105261                         store.clearFilter();
105262                     } else {
105263                         // Clear filter, but supress event so that the BoundList is not immediately updated.
105264                         store.clearFilter(true);
105265                         store.filter(me.displayField, queryString);
105266                     }
105267                 } else {
105268                     // Set flag for onLoad handling to know how the Store was loaded
105269                     me.rawQuery = rawQuery;
105270
105271                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
105272                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
105273                     if (me.pageSize) {
105274                         // if we're paging, we've changed the query so start at page 1.
105275                         me.loadPage(1);
105276                     } else {
105277                         store.load({
105278                             params: me.getParams(queryString)
105279                         });
105280                     }
105281                 }
105282             }
105283
105284             // Clear current selection if it does not match the current value in the field
105285             if (me.getRawValue() !== me.getDisplayValue()) {
105286                 me.ignoreSelection++;
105287                 me.picker.getSelectionModel().deselectAll();
105288                 me.ignoreSelection--;
105289             }
105290
105291             if (isLocalMode) {
105292                 me.doAutoSelect();
105293             }
105294             if (me.typeAhead) {
105295                 me.doTypeAhead();
105296             }
105297         }
105298         return true;
105299     },
105300
105301     loadPage: function(pageNum){
105302         this.store.loadPage(pageNum, {
105303             params: this.getParams(this.lastQuery)
105304         });
105305     },
105306
105307     onPageChange: function(toolbar, newPage){
105308         /*
105309          * Return false here so we can call load ourselves and inject the query param.
105310          * We don't want to do this for every store load since the developer may load
105311          * the store through some other means so we won't add the query param.
105312          */
105313         this.loadPage(newPage);
105314         return false;
105315     },
105316
105317     // private
105318     getParams: function(queryString) {
105319         var params = {},
105320             param = this.queryParam;
105321
105322         if (param) {
105323             params[param] = queryString;
105324         }
105325         return params;
105326     },
105327
105328     /**
105329      * @private
105330      * If the autoSelect config is true, and the picker is open, highlights the first item.
105331      */
105332     doAutoSelect: function() {
105333         var me = this,
105334             picker = me.picker,
105335             lastSelected, itemNode;
105336         if (picker && me.autoSelect && me.store.getCount() > 0) {
105337             // Highlight the last selected item and scroll it into view
105338             lastSelected = picker.getSelectionModel().lastSelected;
105339             itemNode = picker.getNode(lastSelected || 0);
105340             if (itemNode) {
105341                 picker.highlightItem(itemNode);
105342                 picker.listEl.scrollChildIntoView(itemNode, false);
105343             }
105344         }
105345     },
105346
105347     doTypeAhead: function() {
105348         if (!this.typeAheadTask) {
105349             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
105350         }
105351         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
105352             this.typeAheadTask.delay(this.typeAheadDelay);
105353         }
105354     },
105355
105356     onTriggerClick: function() {
105357         var me = this;
105358         if (!me.readOnly && !me.disabled) {
105359             if (me.isExpanded) {
105360                 me.collapse();
105361             } else {
105362                 me.onFocus({});
105363                 if (me.triggerAction === 'all') {
105364                     me.doQuery(me.allQuery, true);
105365                 } else {
105366                     me.doQuery(me.getRawValue(), false, true);
105367                 }
105368             }
105369             me.inputEl.focus();
105370         }
105371     },
105372
105373
105374     // store the last key and doQuery if relevant
105375     onKeyUp: function(e, t) {
105376         var me = this,
105377             key = e.getKey();
105378
105379         if (!me.readOnly && !me.disabled && me.editable) {
105380             me.lastKey = key;
105381             // we put this in a task so that we can cancel it if a user is
105382             // in and out before the queryDelay elapses
105383
105384             // perform query w/ any normal key or backspace or delete
105385             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
105386                 me.doQueryTask.delay(me.queryDelay);
105387             }
105388         }
105389
105390         if (me.enableKeyEvents) {
105391             me.callParent(arguments);
105392         }
105393     },
105394
105395     initEvents: function() {
105396         var me = this;
105397         me.callParent();
105398
105399         /*
105400          * Setup keyboard handling. If enableKeyEvents is true, we already have
105401          * a listener on the inputEl for keyup, so don't create a second.
105402          */
105403         if (!me.enableKeyEvents) {
105404             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
105405         }
105406     },
105407     
105408     onDestroy: function(){
105409         this.bindStore(null);
105410         this.callParent();    
105411     },
105412
105413     createPicker: function() {
105414         var me = this,
105415             picker,
105416             menuCls = Ext.baseCSSPrefix + 'menu',
105417             opts = Ext.apply({
105418                 pickerField: me,
105419                 selModel: {
105420                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
105421                 },
105422                 floating: true,
105423                 hidden: true,
105424                 ownerCt: me.ownerCt,
105425                 cls: me.el.up('.' + menuCls) ? menuCls : '',
105426                 store: me.store,
105427                 displayField: me.displayField,
105428                 focusOnToFront: false,
105429                 pageSize: me.pageSize,
105430                 tpl: me.tpl
105431             }, me.listConfig, me.defaultListConfig);
105432
105433         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
105434         if (me.pageSize) {
105435             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
105436         }
105437
105438         me.mon(picker, {
105439             itemclick: me.onItemClick,
105440             refresh: me.onListRefresh,
105441             scope: me
105442         });
105443
105444         me.mon(picker.getSelectionModel(), {
105445             'beforeselect': me.onBeforeSelect,
105446             'beforedeselect': me.onBeforeDeselect,
105447             'selectionchange': me.onListSelectionChange,
105448             scope: me
105449         });
105450
105451         return picker;
105452     },
105453
105454     alignPicker: function(){
105455         var me = this,
105456             picker = me.picker,
105457             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
105458             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
105459             space = Math.max(heightAbove, heightBelow);
105460
105461         me.callParent();
105462         if (picker.getHeight() > space) {
105463             picker.setHeight(space - 5); // have some leeway so we aren't flush against
105464             me.doAlign();
105465         }
105466     },
105467
105468     onListRefresh: function() {
105469         this.alignPicker();
105470         this.syncSelection();
105471     },
105472
105473     onItemClick: function(picker, record){
105474         /*
105475          * If we're doing single selection, the selection change events won't fire when
105476          * clicking on the selected element. Detect it here.
105477          */
105478         var me = this,
105479             lastSelection = me.lastSelection,
105480             valueField = me.valueField,
105481             selected;
105482
105483         if (!me.multiSelect && lastSelection) {
105484             selected = lastSelection[0];
105485             if (selected && (record.get(valueField) === selected.get(valueField))) {
105486                 // Make sure we also update the display value if it's only partial
105487                 me.displayTplData = [record.data];
105488                 me.setRawValue(me.getDisplayValue());
105489                 me.collapse();
105490             }
105491         }
105492     },
105493
105494     onBeforeSelect: function(list, record) {
105495         return this.fireEvent('beforeselect', this, record, record.index);
105496     },
105497
105498     onBeforeDeselect: function(list, record) {
105499         return this.fireEvent('beforedeselect', this, record, record.index);
105500     },
105501
105502     onListSelectionChange: function(list, selectedRecords) {
105503         var me = this,
105504             isMulti = me.multiSelect,
105505             hasRecords = selectedRecords.length > 0;
105506         // Only react to selection if it is not called from setValue, and if our list is
105507         // expanded (ignores changes to the selection model triggered elsewhere)
105508         if (!me.ignoreSelection && me.isExpanded) {
105509             if (!isMulti) {
105510                 Ext.defer(me.collapse, 1, me);
105511             }
105512             /*
105513              * Only set the value here if we're in multi selection mode or we have
105514              * a selection. Otherwise setValue will be called with an empty value
105515              * which will cause the change event to fire twice.
105516              */
105517             if (isMulti || hasRecords) {
105518                 me.setValue(selectedRecords, false);
105519             }
105520             if (hasRecords) {
105521                 me.fireEvent('select', me, selectedRecords);
105522             }
105523             me.inputEl.focus();
105524         }
105525     },
105526
105527     /**
105528      * @private
105529      * Enables the key nav for the BoundList when it is expanded.
105530      */
105531     onExpand: function() {
105532         var me = this,
105533             keyNav = me.listKeyNav,
105534             selectOnTab = me.selectOnTab,
105535             picker = me.getPicker();
105536
105537         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
105538         if (keyNav) {
105539             keyNav.enable();
105540         } else {
105541             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
105542                 boundList: picker,
105543                 forceKeyDown: true,
105544                 tab: function(e) {
105545                     if (selectOnTab) {
105546                         this.selectHighlighted(e);
105547                         me.triggerBlur();
105548                     }
105549                     // Tab key event is allowed to propagate to field
105550                     return true;
105551                 }
105552             });
105553         }
105554
105555         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
105556         if (selectOnTab) {
105557             me.ignoreMonitorTab = true;
105558         }
105559
105560         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
105561         me.inputEl.focus();
105562     },
105563
105564     /**
105565      * @private
105566      * Disables the key nav for the BoundList when it is collapsed.
105567      */
105568     onCollapse: function() {
105569         var me = this,
105570             keyNav = me.listKeyNav;
105571         if (keyNav) {
105572             keyNav.disable();
105573             me.ignoreMonitorTab = false;
105574         }
105575     },
105576
105577     /**
105578      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
105579      * @param {Object} r
105580      */
105581     select: function(r) {
105582         this.setValue(r, true);
105583     },
105584
105585     /**
105586      * Finds the record by searching for a specific field/value combination.
105587      * @param {String} field The name of the field to test.
105588      * @param {Object} value The value to match the field against.
105589      * @return {Ext.data.Model} The matched record or false.
105590      */
105591     findRecord: function(field, value) {
105592         var ds = this.store,
105593             idx = ds.findExact(field, value);
105594         return idx !== -1 ? ds.getAt(idx) : false;
105595     },
105596
105597     /**
105598      * Finds the record by searching values in the {@link #valueField}.
105599      * @param {Object} value The value to match the field against.
105600      * @return {Ext.data.Model} The matched record or false.
105601      */
105602     findRecordByValue: function(value) {
105603         return this.findRecord(this.valueField, value);
105604     },
105605
105606     /**
105607      * Finds the record by searching values in the {@link #displayField}.
105608      * @param {Object} value The value to match the field against.
105609      * @return {Ext.data.Model} The matched record or false.
105610      */
105611     findRecordByDisplay: function(value) {
105612         return this.findRecord(this.displayField, value);
105613     },
105614
105615     /**
105616      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
105617      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
105618      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
105619      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
105620      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
105621      * or an Array of Strings or Models.
105622      * @return {Ext.form.field.Field} this
105623      */
105624     setValue: function(value, doSelect) {
105625         var me = this,
105626             valueNotFoundText = me.valueNotFoundText,
105627             inputEl = me.inputEl,
105628             i, len, record,
105629             models = [],
105630             displayTplData = [],
105631             processedValue = [];
105632
105633         if (me.store.loading) {
105634             // Called while the Store is loading. Ensure it is processed by the onLoad method.
105635             me.value = value;
105636             me.setHiddenValue(me.value);
105637             return me;
105638         }
105639
105640         // This method processes multi-values, so ensure value is an array.
105641         value = Ext.Array.from(value);
105642
105643         // Loop through values
105644         for (i = 0, len = value.length; i < len; i++) {
105645             record = value[i];
105646             if (!record || !record.isModel) {
105647                 record = me.findRecordByValue(record);
105648             }
105649             // record found, select it.
105650             if (record) {
105651                 models.push(record);
105652                 displayTplData.push(record.data);
105653                 processedValue.push(record.get(me.valueField));
105654             }
105655             // record was not found, this could happen because
105656             // store is not loaded or they set a value not in the store
105657             else {
105658                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
105659                 if (!me.forceSelection) {
105660                     displayTplData.push(value[i]);
105661                     processedValue.push(value[i]);
105662                 }
105663                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
105664                 else if (Ext.isDefined(valueNotFoundText)) {
105665                     displayTplData.push(valueNotFoundText);
105666                 }
105667             }
105668         }
105669
105670         // Set the value of this field. If we are multiselecting, then that is an array.
105671         me.setHiddenValue(processedValue);
105672         me.value = me.multiSelect ? processedValue : processedValue[0];
105673         if (!Ext.isDefined(me.value)) {
105674             me.value = null;
105675         }
105676         me.displayTplData = displayTplData; //store for getDisplayValue method
105677         me.lastSelection = me.valueModels = models;
105678
105679         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
105680             inputEl.removeCls(me.emptyCls);
105681         }
105682
105683         // Calculate raw value from the collection of Model data
105684         me.setRawValue(me.getDisplayValue());
105685         me.checkChange();
105686
105687         if (doSelect !== false) {
105688             me.syncSelection();
105689         }
105690         me.applyEmptyText();
105691
105692         return me;
105693     },
105694
105695     /**
105696      * @private
105697      * Set the value of {@link #hiddenDataEl}
105698      * Dynamically adds and removes input[type=hidden] elements
105699      */
105700     setHiddenValue: function(values){
105701         var me = this, i;
105702         if (!me.hiddenDataEl) {
105703             return;
105704         }
105705         values = Ext.Array.from(values);
105706         var dom = me.hiddenDataEl.dom,
105707             childNodes = dom.childNodes,
105708             input = childNodes[0],
105709             valueCount = values.length,
105710             childrenCount = childNodes.length;
105711         
105712         if (!input && valueCount > 0) {
105713             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
105714             childrenCount = 1;
105715             input = dom.firstChild;
105716         }
105717         while (childrenCount > valueCount) {
105718             dom.removeChild(childNodes[0]);
105719             -- childrenCount;
105720         }
105721         while (childrenCount < valueCount) {
105722             dom.appendChild(input.cloneNode(true));
105723             ++ childrenCount;
105724         }
105725         for (i = 0; i < valueCount; i++) {
105726             childNodes[i].value = values[i];
105727         }
105728     },
105729
105730     /**
105731      * @private Generates the string value to be displayed in the text field for the currently stored value
105732      */
105733     getDisplayValue: function() {
105734         return this.displayTpl.apply(this.displayTplData);
105735     },
105736
105737     getValue: function() {
105738         // If the user has not changed the raw field value since a value was selected from the list,
105739         // then return the structured value from the selection. If the raw field value is different
105740         // than what would be displayed due to selection, return that raw value.
105741         var me = this,
105742             picker = me.picker,
105743             rawValue = me.getRawValue(), //current value of text field
105744             value = me.value; //stored value from last selection or setValue() call
105745
105746         if (me.getDisplayValue() !== rawValue) {
105747             value = rawValue;
105748             me.value = me.displayTplData = me.valueModels = null;
105749             if (picker) {
105750                 me.ignoreSelection++;
105751                 picker.getSelectionModel().deselectAll();
105752                 me.ignoreSelection--;
105753             }
105754         }
105755
105756         return value;
105757     },
105758
105759     getSubmitValue: function() {
105760         return this.getValue();
105761     },
105762
105763     isEqual: function(v1, v2) {
105764         var fromArray = Ext.Array.from,
105765             i, len;
105766
105767         v1 = fromArray(v1);
105768         v2 = fromArray(v2);
105769         len = v1.length;
105770
105771         if (len !== v2.length) {
105772             return false;
105773         }
105774
105775         for(i = 0; i < len; i++) {
105776             if (v2[i] !== v1[i]) {
105777                 return false;
105778             }
105779         }
105780
105781         return true;
105782     },
105783
105784     /**
105785      * Clears any value currently set in the ComboBox.
105786      */
105787     clearValue: function() {
105788         this.setValue([]);
105789     },
105790
105791     /**
105792      * @private Synchronizes the selection in the picker to match the current value of the combobox.
105793      */
105794     syncSelection: function() {
105795         var me = this,
105796             ExtArray = Ext.Array,
105797             picker = me.picker,
105798             selection, selModel;
105799         if (picker) {
105800             // From the value, find the Models that are in the store's current data
105801             selection = [];
105802             ExtArray.forEach(me.valueModels || [], function(value) {
105803                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
105804                     selection.push(value);
105805                 }
105806             });
105807
105808             // Update the selection to match
105809             me.ignoreSelection++;
105810             selModel = picker.getSelectionModel();
105811             selModel.deselectAll();
105812             if (selection.length) {
105813                 selModel.select(selection);
105814             }
105815             me.ignoreSelection--;
105816         }
105817     }
105818 });
105819
105820 /**
105821  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
105822  * to allow browsing and selection of year/months combinations.
105823  */
105824 Ext.define('Ext.picker.Month', {
105825     extend: 'Ext.Component',
105826     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
105827     alias: 'widget.monthpicker',
105828     alternateClassName: 'Ext.MonthPicker',
105829
105830     renderTpl: [
105831         '<div id="{id}-bodyEl" class="{baseCls}-body">',
105832           '<div class="{baseCls}-months">',
105833               '<tpl for="months">',
105834                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
105835               '</tpl>',
105836           '</div>',
105837           '<div class="{baseCls}-years">',
105838               '<div class="{baseCls}-yearnav">',
105839                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
105840                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
105841               '</div>',
105842               '<tpl for="years">',
105843                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
105844               '</tpl>',
105845           '</div>',
105846           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
105847         '</div>',
105848         '<tpl if="showButtons">',
105849           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
105850         '</tpl>'
105851     ],
105852
105853     /**
105854      * @cfg {String} okText The text to display on the ok button.
105855      */
105856     okText: 'OK',
105857
105858     /**
105859      * @cfg {String} cancelText The text to display on the cancel button.
105860      */
105861     cancelText: 'Cancel',
105862
105863     /**
105864      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
105865      */
105866     baseCls: Ext.baseCSSPrefix + 'monthpicker',
105867
105868     /**
105869      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
105870      */
105871     showButtons: true,
105872
105873     /**
105874      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
105875      * <tt>'x-monthpicker-selected'</tt>
105876      */
105877
105878     /**
105879      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
105880      */
105881     width: 178,
105882
105883     // used when attached to date picker which isnt showing buttons
105884     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
105885
105886     // private
105887     totalYears: 10,
105888     yearOffset: 5, // 10 years in total, 2 per row
105889     monthOffset: 6, // 12 months, 2 per row
105890
105891     // private, inherit docs
105892     initComponent: function(){
105893         var me = this;
105894
105895         me.selectedCls = me.baseCls + '-selected';
105896         me.addEvents(
105897             /**
105898              * @event cancelclick
105899              * Fires when the cancel button is pressed.
105900              * @param {Ext.picker.Month} this
105901              */
105902             'cancelclick',
105903
105904             /**
105905              * @event monthclick
105906              * Fires when a month is clicked.
105907              * @param {Ext.picker.Month} this
105908              * @param {Array} value The current value
105909              */
105910             'monthclick',
105911
105912             /**
105913              * @event monthdblclick
105914              * Fires when a month is clicked.
105915              * @param {Ext.picker.Month} this
105916              * @param {Array} value The current value
105917              */
105918             'monthdblclick',
105919
105920             /**
105921              * @event okclick
105922              * Fires when the ok button is pressed.
105923              * @param {Ext.picker.Month} this
105924              * @param {Array} value The current value
105925              */
105926             'okclick',
105927
105928             /**
105929              * @event select
105930              * Fires when a month/year is selected.
105931              * @param {Ext.picker.Month} this
105932              * @param {Array} value The current value
105933              */
105934             'select',
105935
105936             /**
105937              * @event yearclick
105938              * Fires when a year is clicked.
105939              * @param {Ext.picker.Month} this
105940              * @param {Array} value The current value
105941              */
105942             'yearclick',
105943
105944             /**
105945              * @event yeardblclick
105946              * Fires when a year is clicked.
105947              * @param {Ext.picker.Month} this
105948              * @param {Array} value The current value
105949              */
105950             'yeardblclick'
105951         );
105952         if (me.small) {
105953             me.addCls(me.smallCls);
105954         }
105955         me.setValue(me.value);
105956         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
105957         this.callParent();
105958     },
105959
105960     // private, inherit docs
105961     onRender: function(ct, position){
105962         var me = this,
105963             i = 0,
105964             months = [],
105965             shortName = Ext.Date.getShortMonthName,
105966             monthLen = me.monthOffset;
105967
105968         for (; i < monthLen; ++i) {
105969             months.push(shortName(i), shortName(i + monthLen));
105970         }
105971
105972         Ext.apply(me.renderData, {
105973             months: months,
105974             years: me.getYears(),
105975             showButtons: me.showButtons
105976         });
105977
105978         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
105979
105980         me.callParent(arguments);
105981     },
105982
105983     // private, inherit docs
105984     afterRender: function(){
105985         var me = this,
105986             body = me.bodyEl,
105987             buttonsEl = me.buttonsEl;
105988
105989         me.callParent();
105990
105991         me.mon(body, 'click', me.onBodyClick, me);
105992         me.mon(body, 'dblclick', me.onBodyClick, me);
105993
105994         // keep a reference to the year/month elements since we'll be re-using them
105995         me.years = body.select('.' + me.baseCls + '-year a');
105996         me.months = body.select('.' + me.baseCls + '-month a');
105997
105998         if (me.showButtons) {
105999             me.okBtn = Ext.create('Ext.button.Button', {
106000                 text: me.okText,
106001                 renderTo: buttonsEl,
106002                 handler: me.onOkClick,
106003                 scope: me
106004             });
106005             me.cancelBtn = Ext.create('Ext.button.Button', {
106006                 text: me.cancelText,
106007                 renderTo: buttonsEl,
106008                 handler: me.onCancelClick,
106009                 scope: me
106010             });
106011         }
106012
106013         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106014             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
106015         });
106016
106017         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
106018         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106019             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
106020         });
106021         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
106022         me.updateBody();
106023     },
106024
106025     /**
106026      * Set the value for the picker.
106027      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
106028      * it can be an array, with the month as the first index and the year as the second.
106029      * @return {Ext.picker.Month} this
106030      */
106031     setValue: function(value){
106032         var me = this,
106033             active = me.activeYear,
106034             offset = me.monthOffset,
106035             year,
106036             index;
106037
106038         if (!value) {
106039             me.value = [null, null];
106040         } else if (Ext.isDate(value)) {
106041             me.value = [value.getMonth(), value.getFullYear()];
106042         } else {
106043             me.value = [value[0], value[1]];
106044         }
106045
106046         if (me.rendered) {
106047             year = me.value[1];
106048             if (year !== null) {
106049                 if ((year < active || year > active + me.yearOffset)) {
106050                     me.activeYear = year - me.yearOffset + 1;
106051                 }
106052             }
106053             me.updateBody();
106054         }
106055
106056         return me;
106057     },
106058
106059     /**
106060      * Gets the selected value. It is returned as an array [month, year]. It may
106061      * be a partial value, for example [null, 2010]. The month is returned as
106062      * 0 based.
106063      * @return {Number[]} The selected value
106064      */
106065     getValue: function(){
106066         return this.value;
106067     },
106068
106069     /**
106070      * Checks whether the picker has a selection
106071      * @return {Boolean} Returns true if both a month and year have been selected
106072      */
106073     hasSelection: function(){
106074         var value = this.value;
106075         return value[0] !== null && value[1] !== null;
106076     },
106077
106078     /**
106079      * Get an array of years to be pushed in the template. It is not in strict
106080      * numerical order because we want to show them in columns.
106081      * @private
106082      * @return {Number[]} An array of years
106083      */
106084     getYears: function(){
106085         var me = this,
106086             offset = me.yearOffset,
106087             start = me.activeYear, // put the "active" year on the left
106088             end = start + offset,
106089             i = start,
106090             years = [];
106091
106092         for (; i < end; ++i) {
106093             years.push(i, i + offset);
106094         }
106095
106096         return years;
106097     },
106098
106099     /**
106100      * Update the years in the body based on any change
106101      * @private
106102      */
106103     updateBody: function(){
106104         var me = this,
106105             years = me.years,
106106             months = me.months,
106107             yearNumbers = me.getYears(),
106108             cls = me.selectedCls,
106109             value = me.getYear(null),
106110             month = me.value[0],
106111             monthOffset = me.monthOffset,
106112             year;
106113
106114         if (me.rendered) {
106115             years.removeCls(cls);
106116             months.removeCls(cls);
106117             years.each(function(el, all, index){
106118                 year = yearNumbers[index];
106119                 el.dom.innerHTML = year;
106120                 if (year == value) {
106121                     el.dom.className = cls;
106122                 }
106123             });
106124             if (month !== null) {
106125                 if (month < monthOffset) {
106126                     month = month * 2;
106127                 } else {
106128                     month = (month - monthOffset) * 2 + 1;
106129                 }
106130                 months.item(month).addCls(cls);
106131             }
106132         }
106133     },
106134
106135     /**
106136      * Gets the current year value, or the default.
106137      * @private
106138      * @param {Number} defaultValue The default value to use if the year is not defined.
106139      * @param {Number} offset A number to offset the value by
106140      * @return {Number} The year value
106141      */
106142     getYear: function(defaultValue, offset) {
106143         var year = this.value[1];
106144         offset = offset || 0;
106145         return year === null ? defaultValue : year + offset;
106146     },
106147
106148     /**
106149      * React to clicks on the body
106150      * @private
106151      */
106152     onBodyClick: function(e, t) {
106153         var me = this,
106154             isDouble = e.type == 'dblclick';
106155
106156         if (e.getTarget('.' + me.baseCls + '-month')) {
106157             e.stopEvent();
106158             me.onMonthClick(t, isDouble);
106159         } else if (e.getTarget('.' + me.baseCls + '-year')) {
106160             e.stopEvent();
106161             me.onYearClick(t, isDouble);
106162         }
106163     },
106164
106165     /**
106166      * Modify the year display by passing an offset.
106167      * @param {Number} [offset=10] The offset to move by.
106168      */
106169     adjustYear: function(offset){
106170         if (typeof offset != 'number') {
106171             offset = this.totalYears;
106172         }
106173         this.activeYear += offset;
106174         this.updateBody();
106175     },
106176
106177     /**
106178      * React to the ok button being pressed
106179      * @private
106180      */
106181     onOkClick: function(){
106182         this.fireEvent('okclick', this, this.value);
106183     },
106184
106185     /**
106186      * React to the cancel button being pressed
106187      * @private
106188      */
106189     onCancelClick: function(){
106190         this.fireEvent('cancelclick', this);
106191     },
106192
106193     /**
106194      * React to a month being clicked
106195      * @private
106196      * @param {HTMLElement} target The element that was clicked
106197      * @param {Boolean} isDouble True if the event was a doubleclick
106198      */
106199     onMonthClick: function(target, isDouble){
106200         var me = this;
106201         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
106202         me.updateBody();
106203         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106204         me.fireEvent('select', me, me.value);
106205     },
106206
106207     /**
106208      * React to a year being clicked
106209      * @private
106210      * @param {HTMLElement} target The element that was clicked
106211      * @param {Boolean} isDouble True if the event was a doubleclick
106212      */
106213     onYearClick: function(target, isDouble){
106214         var me = this;
106215         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
106216         me.updateBody();
106217         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106218         me.fireEvent('select', me, me.value);
106219
106220     },
106221
106222     /**
106223      * Returns an offsetted number based on the position in the collection. Since our collections aren't
106224      * numerically ordered, this function helps to normalize those differences.
106225      * @private
106226      * @param {Object} index
106227      * @param {Object} offset
106228      * @return {Number} The correctly offsetted number
106229      */
106230     resolveOffset: function(index, offset){
106231         if (index % 2 === 0) {
106232             return (index / 2);
106233         } else {
106234             return offset + Math.floor(index / 2);
106235         }
106236     },
106237
106238     // private, inherit docs
106239     beforeDestroy: function(){
106240         var me = this;
106241         me.years = me.months = null;
106242         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
106243         me.callParent();
106244     }
106245 });
106246
106247 /**
106248  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
106249  * dates in a popup next to the field, but may also be used with other components.
106250  *
106251  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
106252  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
106253  *
106254  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
106255  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
106256  *
106257  * All the string values documented below may be overridden by including an Ext locale file in your page.
106258  *
106259  *     @example
106260  *     Ext.create('Ext.panel.Panel', {
106261  *         title: 'Choose a future date:',
106262  *         width: 200,
106263  *         bodyPadding: 10,
106264  *         renderTo: Ext.getBody(),
106265  *         items: [{
106266  *             xtype: 'datepicker',
106267  *             minDate: new Date(),
106268  *             handler: function(picker, date) {
106269  *                 // do something with the selected date
106270  *             }
106271  *         }]
106272  *     });
106273  */
106274 Ext.define('Ext.picker.Date', {
106275     extend: 'Ext.Component',
106276     requires: [
106277         'Ext.XTemplate',
106278         'Ext.button.Button',
106279         'Ext.button.Split',
106280         'Ext.util.ClickRepeater',
106281         'Ext.util.KeyNav',
106282         'Ext.EventObject',
106283         'Ext.fx.Manager',
106284         'Ext.picker.Month'
106285     ],
106286     alias: 'widget.datepicker',
106287     alternateClassName: 'Ext.DatePicker',
106288
106289     renderTpl: [
106290         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
106291             '<div role="presentation" class="{baseCls}-header">',
106292                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
106293                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
106294                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
106295             '</div>',
106296             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
106297                 '<thead role="presentation"><tr role="presentation">',
106298                     '<tpl for="dayNames">',
106299                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
106300                     '</tpl>',
106301                 '</tr></thead>',
106302                 '<tbody role="presentation"><tr role="presentation">',
106303                     '<tpl for="days">',
106304                         '{#:this.isEndOfWeek}',
106305                         '<td role="gridcell" id="{[Ext.id()]}">',
106306                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
106307                                 '<em role="presentation"><span role="presentation"></span></em>',
106308                             '</a>',
106309                         '</td>',
106310                     '</tpl>',
106311                 '</tr></tbody>',
106312             '</table>',
106313             '<tpl if="showToday">',
106314                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
106315             '</tpl>',
106316         '</div>',
106317         {
106318             firstInitial: function(value) {
106319                 return value.substr(0,1);
106320             },
106321             isEndOfWeek: function(value) {
106322                 // convert from 1 based index to 0 based
106323                 // by decrementing value once.
106324                 value--;
106325                 var end = value % 7 === 0 && value !== 0;
106326                 return end ? '</tr><tr role="row">' : '';
106327             },
106328             longDay: function(value){
106329                 return Ext.Date.format(value, this.longDayFormat);
106330             }
106331         }
106332     ],
106333
106334     ariaTitle: 'Date Picker',
106335
106336     /**
106337      * @cfg {String} todayText
106338      * The text to display on the button that selects the current date
106339      */
106340     todayText : 'Today',
106341
106342     /**
106343      * @cfg {Function} handler
106344      * Optional. A function that will handle the select event of this picker. The handler is passed the following
106345      * parameters:
106346      *
106347      *   - `picker` : Ext.picker.Date
106348      *
106349      * This Date picker.
106350      *
106351      *   - `date` : Date
106352      *
106353      * The selected date.
106354      */
106355
106356     /**
106357      * @cfg {Object} scope
106358      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
106359      * DatePicker instance.
106360      */
106361
106362     /**
106363      * @cfg {String} todayTip
106364      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
106365      * The `{0}` token in string is replaced by today's date.
106366      */
106367     todayTip : '{0} (Spacebar)',
106368
106369     /**
106370      * @cfg {String} minText
106371      * The error text to display if the minDate validation fails.
106372      */
106373     minText : 'This date is before the minimum date',
106374
106375     /**
106376      * @cfg {String} maxText
106377      * The error text to display if the maxDate validation fails.
106378      */
106379     maxText : 'This date is after the maximum date',
106380
106381     /**
106382      * @cfg {String} format
106383      * The default date format string which can be overriden for localization support. The format must be valid
106384      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
106385      */
106386
106387     /**
106388      * @cfg {String} disabledDaysText
106389      * The tooltip to display when the date falls on a disabled day.
106390      */
106391     disabledDaysText : 'Disabled',
106392
106393     /**
106394      * @cfg {String} disabledDatesText
106395      * The tooltip text to display when the date falls on a disabled date.
106396      */
106397     disabledDatesText : 'Disabled',
106398
106399     /**
106400      * @cfg {String[]} monthNames
106401      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
106402      */
106403
106404     /**
106405      * @cfg {String[]} dayNames
106406      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
106407      */
106408
106409     /**
106410      * @cfg {String} nextText
106411      * The next month navigation button tooltip
106412      */
106413     nextText : 'Next Month (Control+Right)',
106414
106415     /**
106416      * @cfg {String} prevText
106417      * The previous month navigation button tooltip
106418      */
106419     prevText : 'Previous Month (Control+Left)',
106420
106421     /**
106422      * @cfg {String} monthYearText
106423      * The header month selector tooltip
106424      */
106425     monthYearText : 'Choose a month (Control+Up/Down to move years)',
106426
106427     /**
106428      * @cfg {Number} startDay
106429      * Day index at which the week should begin, 0-based (defaults to Sunday)
106430      */
106431     startDay : 0,
106432
106433     /**
106434      * @cfg {Boolean} showToday
106435      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
106436      * selects the current date.
106437      */
106438     showToday : true,
106439
106440     /**
106441      * @cfg {Date} [minDate=null]
106442      * Minimum allowable date (JavaScript date object)
106443      */
106444
106445     /**
106446      * @cfg {Date} [maxDate=null]
106447      * Maximum allowable date (JavaScript date object)
106448      */
106449
106450     /**
106451      * @cfg {Number[]} [disabledDays=null]
106452      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
106453      */
106454
106455     /**
106456      * @cfg {RegExp} [disabledDatesRE=null]
106457      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
106458      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
106459      * disabledDates value.
106460      */
106461
106462     /**
106463      * @cfg {String[]} disabledDates
106464      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
106465      * they are very powerful. Some examples:
106466      *
106467      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
106468      *   - ['03/08', '09/16'] would disable those days for every year
106469      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
106470      *   - ['03/../2006'] would disable every day in March 2006
106471      *   - ['^03'] would disable every day in every March
106472      *
106473      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
106474      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
106475      * dot when restricting dates. For example: ['03\\.08\\.03'].
106476      */
106477
106478     /**
106479      * @cfg {Boolean} disableAnim
106480      * True to disable animations when showing the month picker.
106481      */
106482     disableAnim: false,
106483
106484     /**
106485      * @cfg {String} [baseCls='x-datepicker']
106486      * The base CSS class to apply to this components element.
106487      */
106488     baseCls: Ext.baseCSSPrefix + 'datepicker',
106489
106490     /**
106491      * @cfg {String} [selectedCls='x-datepicker-selected']
106492      * The class to apply to the selected cell.
106493      */
106494
106495     /**
106496      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
106497      * The class to apply to disabled cells.
106498      */
106499
106500     /**
106501      * @cfg {String} longDayFormat
106502      * The format for displaying a date in a longer format.
106503      */
106504     longDayFormat: 'F d, Y',
106505
106506     /**
106507      * @cfg {Object} keyNavConfig
106508      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
106509      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
106510      * object will replace default handlers of the same name.
106511      */
106512
106513     /**
106514      * @cfg {Boolean} focusOnShow
106515      * True to automatically focus the picker on show.
106516      */
106517     focusOnShow: false,
106518
106519     // private
106520     // Set by other components to stop the picker focus being updated when the value changes.
106521     focusOnSelect: true,
106522
106523     width: 178,
106524
106525     // default value used to initialise each date in the DatePicker
106526     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
106527     initHour: 12, // 24-hour format
106528
106529     numDays: 42,
106530
106531     // private, inherit docs
106532     initComponent : function() {
106533         var me = this,
106534             clearTime = Ext.Date.clearTime;
106535
106536         me.selectedCls = me.baseCls + '-selected';
106537         me.disabledCellCls = me.baseCls + '-disabled';
106538         me.prevCls = me.baseCls + '-prevday';
106539         me.activeCls = me.baseCls + '-active';
106540         me.nextCls = me.baseCls + '-prevday';
106541         me.todayCls = me.baseCls + '-today';
106542         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
106543         this.callParent();
106544
106545         me.value = me.value ?
106546                  clearTime(me.value, true) : clearTime(new Date());
106547
106548         me.addEvents(
106549             /**
106550              * @event select
106551              * Fires when a date is selected
106552              * @param {Ext.picker.Date} this DatePicker
106553              * @param {Date} date The selected date
106554              */
106555             'select'
106556         );
106557
106558         me.initDisabledDays();
106559     },
106560
106561     // private, inherit docs
106562     onRender : function(container, position){
106563         /*
106564          * days array for looping through 6 full weeks (6 weeks * 7 days)
106565          * Note that we explicitly force the size here so the template creates
106566          * all the appropriate cells.
106567          */
106568
106569         var me = this,
106570             days = new Array(me.numDays),
106571             today = Ext.Date.format(new Date(), me.format);
106572
106573         Ext.applyIf(me, {
106574             renderData: {}
106575         });
106576
106577         Ext.apply(me.renderData, {
106578             dayNames: me.dayNames,
106579             ariaTitle: me.ariaTitle,
106580             value: me.value,
106581             showToday: me.showToday,
106582             prevText: me.prevText,
106583             nextText: me.nextText,
106584             days: days
106585         });
106586         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
106587
106588         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
106589
106590         this.callParent(arguments);
106591         me.el.unselectable();
106592
106593         me.cells = me.eventEl.select('tbody td');
106594         me.textNodes = me.eventEl.query('tbody td span');
106595
106596         me.monthBtn = Ext.create('Ext.button.Split', {
106597             text: '',
106598             tooltip: me.monthYearText,
106599             renderTo: me.middleBtnEl
106600         });
106601         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
106602
106603
106604         me.todayBtn = Ext.create('Ext.button.Button', {
106605             renderTo: me.footerEl,
106606             text: Ext.String.format(me.todayText, today),
106607             tooltip: Ext.String.format(me.todayTip, today),
106608             handler: me.selectToday,
106609             scope: me
106610         });
106611     },
106612
106613     // private, inherit docs
106614     initEvents: function(){
106615         var me = this,
106616             eDate = Ext.Date,
106617             day = eDate.DAY;
106618
106619         this.callParent();
106620
106621         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106622             handler: me.showPrevMonth,
106623             scope: me,
106624             preventDefault: true,
106625             stopDefault: true
106626         });
106627
106628         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106629             handler: me.showNextMonth,
106630             scope: me,
106631             preventDefault:true,
106632             stopDefault:true
106633         });
106634
106635         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
106636             scope: me,
106637             'left' : function(e){
106638                 if(e.ctrlKey){
106639                     me.showPrevMonth();
106640                 }else{
106641                     me.update(eDate.add(me.activeDate, day, -1));
106642                 }
106643             },
106644
106645             'right' : function(e){
106646                 if(e.ctrlKey){
106647                     me.showNextMonth();
106648                 }else{
106649                     me.update(eDate.add(me.activeDate, day, 1));
106650                 }
106651             },
106652
106653             'up' : function(e){
106654                 if(e.ctrlKey){
106655                     me.showNextYear();
106656                 }else{
106657                     me.update(eDate.add(me.activeDate, day, -7));
106658                 }
106659             },
106660
106661             'down' : function(e){
106662                 if(e.ctrlKey){
106663                     me.showPrevYear();
106664                 }else{
106665                     me.update(eDate.add(me.activeDate, day, 7));
106666                 }
106667             },
106668             'pageUp' : me.showNextMonth,
106669             'pageDown' : me.showPrevMonth,
106670             'enter' : function(e){
106671                 e.stopPropagation();
106672                 return true;
106673             }
106674         }, me.keyNavConfig));
106675
106676         if(me.showToday){
106677             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
106678         }
106679         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
106680         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
106681         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
106682         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
106683         me.update(me.value);
106684     },
106685
106686     /**
106687      * Setup the disabled dates regex based on config options
106688      * @private
106689      */
106690     initDisabledDays : function(){
106691         var me = this,
106692             dd = me.disabledDates,
106693             re = '(?:',
106694             len;
106695
106696         if(!me.disabledDatesRE && dd){
106697                 len = dd.length - 1;
106698
106699             Ext.each(dd, function(d, i){
106700                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
106701                 if(i != len){
106702                     re += '|';
106703                 }
106704             }, me);
106705             me.disabledDatesRE = new RegExp(re + ')');
106706         }
106707     },
106708
106709     /**
106710      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
106711      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
106712      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
106713      * @return {Ext.picker.Date} this
106714      */
106715     setDisabledDates : function(dd){
106716         var me = this;
106717
106718         if(Ext.isArray(dd)){
106719             me.disabledDates = dd;
106720             me.disabledDatesRE = null;
106721         }else{
106722             me.disabledDatesRE = dd;
106723         }
106724         me.initDisabledDays();
106725         me.update(me.value, true);
106726         return me;
106727     },
106728
106729     /**
106730      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
106731      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
106732      * on supported values.
106733      * @return {Ext.picker.Date} this
106734      */
106735     setDisabledDays : function(dd){
106736         this.disabledDays = dd;
106737         return this.update(this.value, true);
106738     },
106739
106740     /**
106741      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
106742      * @param {Date} value The minimum date that can be selected
106743      * @return {Ext.picker.Date} this
106744      */
106745     setMinDate : function(dt){
106746         this.minDate = dt;
106747         return this.update(this.value, true);
106748     },
106749
106750     /**
106751      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
106752      * @param {Date} value The maximum date that can be selected
106753      * @return {Ext.picker.Date} this
106754      */
106755     setMaxDate : function(dt){
106756         this.maxDate = dt;
106757         return this.update(this.value, true);
106758     },
106759
106760     /**
106761      * Sets the value of the date field
106762      * @param {Date} value The date to set
106763      * @return {Ext.picker.Date} this
106764      */
106765     setValue : function(value){
106766         this.value = Ext.Date.clearTime(value, true);
106767         return this.update(this.value);
106768     },
106769
106770     /**
106771      * Gets the current selected value of the date field
106772      * @return {Date} The selected date
106773      */
106774     getValue : function(){
106775         return this.value;
106776     },
106777
106778     // private
106779     focus : function(){
106780         this.update(this.activeDate);
106781     },
106782
106783     // private, inherit docs
106784     onEnable: function(){
106785         this.callParent();
106786         this.setDisabledStatus(false);
106787         this.update(this.activeDate);
106788
106789     },
106790
106791     // private, inherit docs
106792     onDisable : function(){
106793         this.callParent();
106794         this.setDisabledStatus(true);
106795     },
106796
106797     /**
106798      * Set the disabled state of various internal components
106799      * @private
106800      * @param {Boolean} disabled
106801      */
106802     setDisabledStatus : function(disabled){
106803         var me = this;
106804
106805         me.keyNav.setDisabled(disabled);
106806         me.prevRepeater.setDisabled(disabled);
106807         me.nextRepeater.setDisabled(disabled);
106808         if (me.showToday) {
106809             me.todayKeyListener.setDisabled(disabled);
106810             me.todayBtn.setDisabled(disabled);
106811         }
106812     },
106813
106814     /**
106815      * Get the current active date.
106816      * @private
106817      * @return {Date} The active date
106818      */
106819     getActive: function(){
106820         return this.activeDate || this.value;
106821     },
106822
106823     /**
106824      * Run any animation required to hide/show the month picker.
106825      * @private
106826      * @param {Boolean} isHide True if it's a hide operation
106827      */
106828     runAnimation: function(isHide){
106829         var picker = this.monthPicker,
106830             options = {
106831                 duration: 200,
106832                 callback: function(){
106833                     if (isHide) {
106834                         picker.hide();
106835                     } else {
106836                         picker.show();
106837                     }
106838                 }
106839             };
106840
106841         if (isHide) {
106842             picker.el.slideOut('t', options);
106843         } else {
106844             picker.el.slideIn('t', options);
106845         }
106846     },
106847
106848     /**
106849      * Hides the month picker, if it's visible.
106850      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106851      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106852      * whether to animate or not.
106853      * @return {Ext.picker.Date} this
106854      */
106855     hideMonthPicker : function(animate){
106856         var me = this,
106857             picker = me.monthPicker;
106858
106859         if (picker) {
106860             if (me.shouldAnimate(animate)) {
106861                 me.runAnimation(true);
106862             } else {
106863                 picker.hide();
106864             }
106865         }
106866         return me;
106867     },
106868
106869     /**
106870      * Show the month picker
106871      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106872      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106873      * whether to animate or not.
106874      * @return {Ext.picker.Date} this
106875      */
106876     showMonthPicker : function(animate){
106877         var me = this,
106878             picker;
106879         
106880         if (me.rendered && !me.disabled) {
106881             picker = me.createMonthPicker();
106882             picker.setValue(me.getActive());
106883             picker.setSize(me.getSize());
106884             picker.setPosition(-1, -1);
106885             if (me.shouldAnimate(animate)) {
106886                 me.runAnimation(false);
106887             } else {
106888                 picker.show();
106889             }
106890         }
106891         return me;
106892     },
106893     
106894     /**
106895      * Checks whether a hide/show action should animate
106896      * @private
106897      * @param {Boolean} [animate] A possible animation value
106898      * @return {Boolean} Whether to animate the action
106899      */
106900     shouldAnimate: function(animate){
106901         return Ext.isDefined(animate) ? animate : !this.disableAnim;
106902     },
106903
106904     /**
106905      * Create the month picker instance
106906      * @private
106907      * @return {Ext.picker.Month} picker
106908      */
106909     createMonthPicker: function(){
106910         var me = this,
106911             picker = me.monthPicker;
106912
106913         if (!picker) {
106914             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
106915                 renderTo: me.el,
106916                 floating: true,
106917                 shadow: false,
106918                 small: me.showToday === false,
106919                 listeners: {
106920                     scope: me,
106921                     cancelclick: me.onCancelClick,
106922                     okclick: me.onOkClick,
106923                     yeardblclick: me.onOkClick,
106924                     monthdblclick: me.onOkClick
106925                 }
106926             });
106927             if (!me.disableAnim) {
106928                 // hide the element if we're animating to prevent an initial flicker
106929                 picker.el.setStyle('display', 'none');
106930             }
106931             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
106932         }
106933         return picker;
106934     },
106935
106936     /**
106937      * Respond to an ok click on the month picker
106938      * @private
106939      */
106940     onOkClick: function(picker, value){
106941         var me = this,
106942             month = value[0],
106943             year = value[1],
106944             date = new Date(year, month, me.getActive().getDate());
106945
106946         if (date.getMonth() !== month) {
106947             // 'fix' the JS rolling date conversion if needed
106948             date = new Date(year, month, 1).getLastDateOfMonth();
106949         }
106950         me.update(date);
106951         me.hideMonthPicker();
106952     },
106953
106954     /**
106955      * Respond to a cancel click on the month picker
106956      * @private
106957      */
106958     onCancelClick: function(){
106959         this.hideMonthPicker();
106960     },
106961
106962     /**
106963      * Show the previous month.
106964      * @param {Object} e
106965      * @return {Ext.picker.Date} this
106966      */
106967     showPrevMonth : function(e){
106968         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
106969     },
106970
106971     /**
106972      * Show the next month.
106973      * @param {Object} e
106974      * @return {Ext.picker.Date} this
106975      */
106976     showNextMonth : function(e){
106977         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
106978     },
106979
106980     /**
106981      * Show the previous year.
106982      * @return {Ext.picker.Date} this
106983      */
106984     showPrevYear : function(){
106985         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
106986     },
106987
106988     /**
106989      * Show the next year.
106990      * @return {Ext.picker.Date} this
106991      */
106992     showNextYear : function(){
106993         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
106994     },
106995
106996     /**
106997      * Respond to the mouse wheel event
106998      * @private
106999      * @param {Ext.EventObject} e
107000      */
107001     handleMouseWheel : function(e){
107002         e.stopEvent();
107003         if(!this.disabled){
107004             var delta = e.getWheelDelta();
107005             if(delta > 0){
107006                 this.showPrevMonth();
107007             } else if(delta < 0){
107008                 this.showNextMonth();
107009             }
107010         }
107011     },
107012
107013     /**
107014      * Respond to a date being clicked in the picker
107015      * @private
107016      * @param {Ext.EventObject} e
107017      * @param {HTMLElement} t
107018      */
107019     handleDateClick : function(e, t){
107020         var me = this,
107021             handler = me.handler;
107022
107023         e.stopEvent();
107024         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
107025             me.cancelFocus = me.focusOnSelect === false;
107026             me.setValue(new Date(t.dateValue));
107027             delete me.cancelFocus;
107028             me.fireEvent('select', me, me.value);
107029             if (handler) {
107030                 handler.call(me.scope || me, me, me.value);
107031             }
107032             // event handling is turned off on hide
107033             // when we are using the picker in a field
107034             // therefore onSelect comes AFTER the select
107035             // event.
107036             me.onSelect();
107037         }
107038     },
107039
107040     /**
107041      * Perform any post-select actions
107042      * @private
107043      */
107044     onSelect: function() {
107045         if (this.hideOnSelect) {
107046              this.hide();
107047          }
107048     },
107049
107050     /**
107051      * Sets the current value to today.
107052      * @return {Ext.picker.Date} this
107053      */
107054     selectToday : function(){
107055         var me = this,
107056             btn = me.todayBtn,
107057             handler = me.handler;
107058
107059         if(btn && !btn.disabled){
107060             me.setValue(Ext.Date.clearTime(new Date()));
107061             me.fireEvent('select', me, me.value);
107062             if (handler) {
107063                 handler.call(me.scope || me, me, me.value);
107064             }
107065             me.onSelect();
107066         }
107067         return me;
107068     },
107069
107070     /**
107071      * Update the selected cell
107072      * @private
107073      * @param {Date} date The new date
107074      * @param {Date} active The active date
107075      */
107076     selectedUpdate: function(date, active){
107077         var me = this,
107078             t = date.getTime(),
107079             cells = me.cells,
107080             cls = me.selectedCls;
107081
107082         cells.removeCls(cls);
107083         cells.each(function(c){
107084             if (c.dom.firstChild.dateValue == t) {
107085                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
107086                 c.addCls(cls);
107087                 if(me.isVisible() && !me.cancelFocus){
107088                     Ext.fly(c.dom.firstChild).focus(50);
107089                 }
107090                 return false;
107091             }
107092         }, this);
107093     },
107094
107095     /**
107096      * Update the contents of the picker for a new month
107097      * @private
107098      * @param {Date} date The new date
107099      * @param {Date} active The active date
107100      */
107101     fullUpdate: function(date, active){
107102         var me = this,
107103             cells = me.cells.elements,
107104             textNodes = me.textNodes,
107105             disabledCls = me.disabledCellCls,
107106             eDate = Ext.Date,
107107             i = 0,
107108             extraDays = 0,
107109             visible = me.isVisible(),
107110             sel = +eDate.clearTime(date, true),
107111             today = +eDate.clearTime(new Date()),
107112             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
107113             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
107114             ddMatch = me.disabledDatesRE,
107115             ddText = me.disabledDatesText,
107116             ddays = me.disabledDays ? me.disabledDays.join('') : false,
107117             ddaysText = me.disabledDaysText,
107118             format = me.format,
107119             days = eDate.getDaysInMonth(date),
107120             firstOfMonth = eDate.getFirstDateOfMonth(date),
107121             startingPos = firstOfMonth.getDay() - me.startDay,
107122             previousMonth = eDate.add(date, eDate.MONTH, -1),
107123             longDayFormat = me.longDayFormat,
107124             prevStart,
107125             current,
107126             disableToday,
107127             tempDate,
107128             setCellClass,
107129             html,
107130             cls,
107131             formatValue,
107132             value;
107133
107134         if (startingPos < 0) {
107135             startingPos += 7;
107136         }
107137
107138         days += startingPos;
107139         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
107140         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
107141
107142         if (me.showToday) {
107143             tempDate = eDate.clearTime(new Date());
107144             disableToday = (tempDate < min || tempDate > max ||
107145                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
107146                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
107147
107148             if (!me.disabled) {
107149                 me.todayBtn.setDisabled(disableToday);
107150                 me.todayKeyListener.setDisabled(disableToday);
107151             }
107152         }
107153
107154         setCellClass = function(cell){
107155             value = +eDate.clearTime(current, true);
107156             cell.title = eDate.format(current, longDayFormat);
107157             // store dateValue number as an expando
107158             cell.firstChild.dateValue = value;
107159             if(value == today){
107160                 cell.className += ' ' + me.todayCls;
107161                 cell.title = me.todayText;
107162             }
107163             if(value == sel){
107164                 cell.className += ' ' + me.selectedCls;
107165                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
107166                 if (visible && me.floating) {
107167                     Ext.fly(cell.firstChild).focus(50);
107168                 }
107169             }
107170             // disabling
107171             if(value < min) {
107172                 cell.className = disabledCls;
107173                 cell.title = me.minText;
107174                 return;
107175             }
107176             if(value > max) {
107177                 cell.className = disabledCls;
107178                 cell.title = me.maxText;
107179                 return;
107180             }
107181             if(ddays){
107182                 if(ddays.indexOf(current.getDay()) != -1){
107183                     cell.title = ddaysText;
107184                     cell.className = disabledCls;
107185                 }
107186             }
107187             if(ddMatch && format){
107188                 formatValue = eDate.dateFormat(current, format);
107189                 if(ddMatch.test(formatValue)){
107190                     cell.title = ddText.replace('%0', formatValue);
107191                     cell.className = disabledCls;
107192                 }
107193             }
107194         };
107195
107196         for(; i < me.numDays; ++i) {
107197             if (i < startingPos) {
107198                 html = (++prevStart);
107199                 cls = me.prevCls;
107200             } else if (i >= days) {
107201                 html = (++extraDays);
107202                 cls = me.nextCls;
107203             } else {
107204                 html = i - startingPos + 1;
107205                 cls = me.activeCls;
107206             }
107207             textNodes[i].innerHTML = html;
107208             cells[i].className = cls;
107209             current.setDate(current.getDate() + 1);
107210             setCellClass(cells[i]);
107211         }
107212
107213         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
107214     },
107215
107216     /**
107217      * Update the contents of the picker
107218      * @private
107219      * @param {Date} date The new date
107220      * @param {Boolean} forceRefresh True to force a full refresh
107221      */
107222     update : function(date, forceRefresh){
107223         var me = this,
107224             active = me.activeDate;
107225
107226         if (me.rendered) {
107227             me.activeDate = date;
107228             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
107229                 me.selectedUpdate(date, active);
107230             } else {
107231                 me.fullUpdate(date, active);
107232             }
107233         }
107234         return me;
107235     },
107236
107237     // private, inherit docs
107238     beforeDestroy : function() {
107239         var me = this;
107240
107241         if (me.rendered) {
107242             Ext.destroy(
107243                 me.todayKeyListener,
107244                 me.keyNav,
107245                 me.monthPicker,
107246                 me.monthBtn,
107247                 me.nextRepeater,
107248                 me.prevRepeater,
107249                 me.todayBtn
107250             );
107251             delete me.textNodes;
107252             delete me.cells.elements;
107253         }
107254         me.callParent();
107255     },
107256
107257     // private, inherit docs
107258     onShow: function() {
107259         this.callParent(arguments);
107260         if (this.focusOnShow) {
107261             this.focus();
107262         }
107263     }
107264 },
107265
107266 // After dependencies have loaded:
107267 function() {
107268     var proto = this.prototype;
107269
107270     proto.monthNames = Ext.Date.monthNames;
107271
107272     proto.dayNames = Ext.Date.dayNames;
107273
107274     proto.format = Ext.Date.defaultFormat;
107275 });
107276
107277 /**
107278  * @docauthor Jason Johnston <jason@sencha.com>
107279  *
107280  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
107281  * validation.
107282  *
107283  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
107284  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
107285  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
107286  *
107287  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
107288  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
107289  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
107290  *
107291  * # Example usage
107292  *
107293  *     @example
107294  *     Ext.create('Ext.form.Panel', {
107295  *         renderTo: Ext.getBody(),
107296  *         width: 300,
107297  *         bodyPadding: 10,
107298  *         title: 'Dates',
107299  *         items: [{
107300  *             xtype: 'datefield',
107301  *             anchor: '100%',
107302  *             fieldLabel: 'From',
107303  *             name: 'from_date',
107304  *             maxValue: new Date()  // limited to the current date or prior
107305  *         }, {
107306  *             xtype: 'datefield',
107307  *             anchor: '100%',
107308  *             fieldLabel: 'To',
107309  *             name: 'to_date',
107310  *             value: new Date()  // defaults to today
107311  *         }]
107312  *     });
107313  *
107314  * # Date Formats Examples
107315  *
107316  * This example shows a couple of different date format parsing scenarios. Both use custom date format
107317  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
107318  *
107319  *     @example
107320  *     Ext.create('Ext.form.Panel', {
107321  *         renderTo: Ext.getBody(),
107322  *         width: 300,
107323  *         bodyPadding: 10,
107324  *         title: 'Dates',
107325  *         items: [{
107326  *             xtype: 'datefield',
107327  *             anchor: '100%',
107328  *             fieldLabel: 'Date',
107329  *             name: 'date',
107330  *             // The value matches the format; will be parsed and displayed using that format.
107331  *             format: 'm d Y',
107332  *             value: '2 4 1978'
107333  *         }, {
107334  *             xtype: 'datefield',
107335  *             anchor: '100%',
107336  *             fieldLabel: 'Date',
107337  *             name: 'date',
107338  *             // The value does not match the format, but does match an altFormat; will be parsed
107339  *             // using the altFormat and displayed using the format.
107340  *             format: 'm d Y',
107341  *             altFormats: 'm,d,Y|m.d.Y',
107342  *             value: '2.4.1978'
107343  *         }]
107344  *     });
107345  */
107346 Ext.define('Ext.form.field.Date', {
107347     extend:'Ext.form.field.Picker',
107348     alias: 'widget.datefield',
107349     requires: ['Ext.picker.Date'],
107350     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
107351
107352     /**
107353      * @cfg {String} format
107354      * The default date format string which can be overriden for localization support. The format must be valid
107355      * according to {@link Ext.Date#parse}.
107356      */
107357     format : "m/d/Y",
107358     /**
107359      * @cfg {String} altFormats
107360      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
107361      * format.
107362      */
107363     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",
107364     /**
107365      * @cfg {String} disabledDaysText
107366      * The tooltip to display when the date falls on a disabled day.
107367      */
107368     disabledDaysText : "Disabled",
107369     /**
107370      * @cfg {String} disabledDatesText
107371      * The tooltip text to display when the date falls on a disabled date.
107372      */
107373     disabledDatesText : "Disabled",
107374     /**
107375      * @cfg {String} minText
107376      * The error text to display when the date in the cell is before {@link #minValue}.
107377      */
107378     minText : "The date in this field must be equal to or after {0}",
107379     /**
107380      * @cfg {String} maxText
107381      * The error text to display when the date in the cell is after {@link #maxValue}.
107382      */
107383     maxText : "The date in this field must be equal to or before {0}",
107384     /**
107385      * @cfg {String} invalidText
107386      * The error text to display when the date in the field is invalid.
107387      */
107388     invalidText : "{0} is not a valid date - it must be in the format {1}",
107389     /**
107390      * @cfg {String} [triggerCls='x-form-date-trigger']
107391      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
107392      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
107393      */
107394     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
107395     /**
107396      * @cfg {Boolean} showToday
107397      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
107398      * spacebar that selects the current date.
107399      */
107400     showToday : true,
107401     /**
107402      * @cfg {Date/String} minValue
107403      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
107404      */
107405     /**
107406      * @cfg {Date/String} maxValue
107407      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
107408      */
107409     /**
107410      * @cfg {Number[]} disabledDays
107411      * An array of days to disable, 0 based. Some examples:
107412      *
107413      *     // disable Sunday and Saturday:
107414      *     disabledDays:  [0, 6]
107415      *     // disable weekdays:
107416      *     disabledDays: [1,2,3,4,5]
107417      */
107418     /**
107419      * @cfg {String[]} disabledDates
107420      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
107421      * they are very powerful. Some examples:
107422      *
107423      *     // disable these exact dates:
107424      *     disabledDates: ["03/08/2003", "09/16/2003"]
107425      *     // disable these days for every year:
107426      *     disabledDates: ["03/08", "09/16"]
107427      *     // only match the beginning (useful if you are using short years):
107428      *     disabledDates: ["^03/08"]
107429      *     // disable every day in March 2006:
107430      *     disabledDates: ["03/../2006"]
107431      *     // disable every day in every March:
107432      *     disabledDates: ["^03"]
107433      *
107434      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
107435      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
107436      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
107437      */
107438
107439     /**
107440      * @cfg {String} submitFormat
107441      * The date format string which will be submitted to the server. The format must be valid according to {@link
107442      * Ext.Date#parse} (defaults to {@link #format}).
107443      */
107444
107445     // in the absence of a time value, a default value of 12 noon will be used
107446     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
107447     initTime: '12', // 24 hour format
107448
107449     initTimeFormat: 'H',
107450
107451     matchFieldWidth: false,
107452     /**
107453      * @cfg {Number} startDay
107454      * Day index at which the week should begin, 0-based (defaults to Sunday)
107455      */
107456     startDay: 0,
107457
107458     initComponent : function(){
107459         var me = this,
107460             isString = Ext.isString,
107461             min, max;
107462
107463         min = me.minValue;
107464         max = me.maxValue;
107465         if(isString(min)){
107466             me.minValue = me.parseDate(min);
107467         }
107468         if(isString(max)){
107469             me.maxValue = me.parseDate(max);
107470         }
107471         me.disabledDatesRE = null;
107472         me.initDisabledDays();
107473
107474         me.callParent();
107475     },
107476
107477     initValue: function() {
107478         var me = this,
107479             value = me.value;
107480
107481         // If a String value was supplied, try to convert it to a proper Date
107482         if (Ext.isString(value)) {
107483             me.value = me.rawToValue(value);
107484         }
107485
107486         me.callParent();
107487     },
107488
107489     // private
107490     initDisabledDays : function(){
107491         if(this.disabledDates){
107492             var dd = this.disabledDates,
107493                 len = dd.length - 1,
107494                 re = "(?:";
107495
107496             Ext.each(dd, function(d, i){
107497                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
107498                 if (i !== len) {
107499                     re += '|';
107500                 }
107501             }, this);
107502             this.disabledDatesRE = new RegExp(re + ')');
107503         }
107504     },
107505
107506     /**
107507      * Replaces any existing disabled dates with new values and refreshes the Date picker.
107508      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
107509      * supported values) used to disable a pattern of dates.
107510      */
107511     setDisabledDates : function(dd){
107512         var me = this,
107513             picker = me.picker;
107514
107515         me.disabledDates = dd;
107516         me.initDisabledDays();
107517         if (picker) {
107518             picker.setDisabledDates(me.disabledDatesRE);
107519         }
107520     },
107521
107522     /**
107523      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
107524      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
107525      * supported values.
107526      */
107527     setDisabledDays : function(dd){
107528         var picker = this.picker;
107529
107530         this.disabledDays = dd;
107531         if (picker) {
107532             picker.setDisabledDays(dd);
107533         }
107534     },
107535
107536     /**
107537      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
107538      * @param {Date} value The minimum date that can be selected
107539      */
107540     setMinValue : function(dt){
107541         var me = this,
107542             picker = me.picker,
107543             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107544
107545         me.minValue = minValue;
107546         if (picker) {
107547             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
107548             picker.setMinDate(minValue);
107549         }
107550     },
107551
107552     /**
107553      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
107554      * @param {Date} value The maximum date that can be selected
107555      */
107556     setMaxValue : function(dt){
107557         var me = this,
107558             picker = me.picker,
107559             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107560
107561         me.maxValue = maxValue;
107562         if (picker) {
107563             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
107564             picker.setMaxDate(maxValue);
107565         }
107566     },
107567
107568     /**
107569      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
107570      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
107571      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
107572      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
107573      * @param {Object} [value] The value to get errors for (defaults to the current field value)
107574      * @return {String[]} All validation errors for this field
107575      */
107576     getErrors: function(value) {
107577         var me = this,
107578             format = Ext.String.format,
107579             clearTime = Ext.Date.clearTime,
107580             errors = me.callParent(arguments),
107581             disabledDays = me.disabledDays,
107582             disabledDatesRE = me.disabledDatesRE,
107583             minValue = me.minValue,
107584             maxValue = me.maxValue,
107585             len = disabledDays ? disabledDays.length : 0,
107586             i = 0,
107587             svalue,
107588             fvalue,
107589             day,
107590             time;
107591
107592         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
107593
107594         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
107595              return errors;
107596         }
107597
107598         svalue = value;
107599         value = me.parseDate(value);
107600         if (!value) {
107601             errors.push(format(me.invalidText, svalue, me.format));
107602             return errors;
107603         }
107604
107605         time = value.getTime();
107606         if (minValue && time < clearTime(minValue).getTime()) {
107607             errors.push(format(me.minText, me.formatDate(minValue)));
107608         }
107609
107610         if (maxValue && time > clearTime(maxValue).getTime()) {
107611             errors.push(format(me.maxText, me.formatDate(maxValue)));
107612         }
107613
107614         if (disabledDays) {
107615             day = value.getDay();
107616
107617             for(; i < len; i++) {
107618                 if (day === disabledDays[i]) {
107619                     errors.push(me.disabledDaysText);
107620                     break;
107621                 }
107622             }
107623         }
107624
107625         fvalue = me.formatDate(value);
107626         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
107627             errors.push(format(me.disabledDatesText, fvalue));
107628         }
107629
107630         return errors;
107631     },
107632
107633     rawToValue: function(rawValue) {
107634         return this.parseDate(rawValue) || rawValue || null;
107635     },
107636
107637     valueToRaw: function(value) {
107638         return this.formatDate(this.parseDate(value));
107639     },
107640
107641     /**
107642      * @method setValue
107643      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
107644      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
107645      * format used is "m/d/Y").
107646      *
107647      * Usage:
107648      *
107649      *     //All of these calls set the same date value (May 4, 2006)
107650      *
107651      *     //Pass a date object:
107652      *     var dt = new Date('5/4/2006');
107653      *     dateField.setValue(dt);
107654      *
107655      *     //Pass a date string (default format):
107656      *     dateField.setValue('05/04/2006');
107657      *
107658      *     //Pass a date string (custom format):
107659      *     dateField.format = 'Y-m-d';
107660      *     dateField.setValue('2006-05-04');
107661      *
107662      * @param {String/Date} date The date or valid date string
107663      * @return {Ext.form.field.Date} this
107664      */
107665
107666     /**
107667      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
107668      * @param {String} value The value to attempt to parse
107669      * @param {String} format A valid date format (see {@link Ext.Date#parse})
107670      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
107671      */
107672     safeParse : function(value, format) {
107673         var me = this,
107674             utilDate = Ext.Date,
107675             parsedDate,
107676             result = null;
107677
107678         if (utilDate.formatContainsHourInfo(format)) {
107679             // if parse format contains hour information, no DST adjustment is necessary
107680             result = utilDate.parse(value, format);
107681         } else {
107682             // set time to 12 noon, then clear the time
107683             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
107684             if (parsedDate) {
107685                 result = utilDate.clearTime(parsedDate);
107686             }
107687         }
107688         return result;
107689     },
107690
107691     // @private
107692     getSubmitValue: function() {
107693         var format = this.submitFormat || this.format,
107694             value = this.getValue();
107695
107696         return value ? Ext.Date.format(value, format) : '';
107697     },
107698
107699     /**
107700      * @private
107701      */
107702     parseDate : function(value) {
107703         if(!value || Ext.isDate(value)){
107704             return value;
107705         }
107706
107707         var me = this,
107708             val = me.safeParse(value, me.format),
107709             altFormats = me.altFormats,
107710             altFormatsArray = me.altFormatsArray,
107711             i = 0,
107712             len;
107713
107714         if (!val && altFormats) {
107715             altFormatsArray = altFormatsArray || altFormats.split('|');
107716             len = altFormatsArray.length;
107717             for (; i < len && !val; ++i) {
107718                 val = me.safeParse(value, altFormatsArray[i]);
107719             }
107720         }
107721         return val;
107722     },
107723
107724     // private
107725     formatDate : function(date){
107726         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
107727     },
107728
107729     createPicker: function() {
107730         var me = this,
107731             format = Ext.String.format;
107732
107733         return Ext.create('Ext.picker.Date', {
107734             pickerField: me,
107735             ownerCt: me.ownerCt,
107736             renderTo: document.body,
107737             floating: true,
107738             hidden: true,
107739             focusOnShow: true,
107740             minDate: me.minValue,
107741             maxDate: me.maxValue,
107742             disabledDatesRE: me.disabledDatesRE,
107743             disabledDatesText: me.disabledDatesText,
107744             disabledDays: me.disabledDays,
107745             disabledDaysText: me.disabledDaysText,
107746             format: me.format,
107747             showToday: me.showToday,
107748             startDay: me.startDay,
107749             minText: format(me.minText, me.formatDate(me.minValue)),
107750             maxText: format(me.maxText, me.formatDate(me.maxValue)),
107751             listeners: {
107752                 scope: me,
107753                 select: me.onSelect
107754             },
107755             keyNavConfig: {
107756                 esc: function() {
107757                     me.collapse();
107758                 }
107759             }
107760         });
107761     },
107762
107763     onSelect: function(m, d) {
107764         var me = this;
107765
107766         me.setValue(d);
107767         me.fireEvent('select', me, d);
107768         me.collapse();
107769     },
107770
107771     /**
107772      * @private
107773      * Sets the Date picker's value to match the current field value when expanding.
107774      */
107775     onExpand: function() {
107776         var value = this.getValue();
107777         this.picker.setValue(Ext.isDate(value) ? value : new Date());
107778     },
107779
107780     /**
107781      * @private
107782      * Focuses the field when collapsing the Date picker.
107783      */
107784     onCollapse: function() {
107785         this.focus(false, 60);
107786     },
107787
107788     // private
107789     beforeBlur : function(){
107790         var me = this,
107791             v = me.parseDate(me.getRawValue()),
107792             focusTask = me.focusTask;
107793
107794         if (focusTask) {
107795             focusTask.cancel();
107796         }
107797
107798         if (v) {
107799             me.setValue(v);
107800         }
107801     }
107802
107803     /**
107804      * @hide
107805      * @cfg {Boolean} grow
107806      */
107807     /**
107808      * @hide
107809      * @cfg {Number} growMin
107810      */
107811     /**
107812      * @hide
107813      * @cfg {Number} growMax
107814      */
107815     /**
107816      * @hide
107817      * @method autoSize
107818      */
107819 });
107820
107821 /**
107822  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
107823  * 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
107824  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
107825  * to be rendered.
107826  *
107827  * If you have more complex content, or need to include components within the displayed content, also consider using a
107828  * {@link Ext.form.FieldContainer} instead.
107829  *
107830  * Example:
107831  *
107832  *     @example
107833  *     Ext.create('Ext.form.Panel', {
107834  *         renderTo: Ext.getBody(),
107835  *         width: 175,
107836  *         height: 120,
107837  *         bodyPadding: 10,
107838  *         title: 'Final Score',
107839  *         items: [{
107840  *             xtype: 'displayfield',
107841  *             fieldLabel: 'Home',
107842  *             name: 'home_score',
107843  *             value: '10'
107844  *         }, {
107845  *             xtype: 'displayfield',
107846  *             fieldLabel: 'Visitor',
107847  *             name: 'visitor_score',
107848  *             value: '11'
107849  *         }],
107850  *         buttons: [{
107851  *             text: 'Update',
107852  *         }]
107853  *     });
107854  */
107855 Ext.define('Ext.form.field.Display', {
107856     extend:'Ext.form.field.Base',
107857     alias: 'widget.displayfield',
107858     requires: ['Ext.util.Format', 'Ext.XTemplate'],
107859     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
107860     fieldSubTpl: [
107861         '<div id="{id}" class="{fieldCls}"></div>',
107862         {
107863             compiled: true,
107864             disableFormats: true
107865         }
107866     ],
107867
107868     /**
107869      * @cfg {String} [fieldCls="x-form-display-field"]
107870      * The default CSS class for the field.
107871      */
107872     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
107873
107874     /**
107875      * @cfg {Boolean} htmlEncode
107876      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
107877      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
107878      */
107879     htmlEncode: false,
107880
107881     validateOnChange: false,
107882
107883     initEvents: Ext.emptyFn,
107884
107885     submitValue: false,
107886
107887     isValid: function() {
107888         return true;
107889     },
107890
107891     validate: function() {
107892         return true;
107893     },
107894
107895     getRawValue: function() {
107896         return this.rawValue;
107897     },
107898
107899     setRawValue: function(value) {
107900         var me = this;
107901         value = Ext.value(value, '');
107902         me.rawValue = value;
107903         if (me.rendered) {
107904             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
107905         }
107906         return value;
107907     },
107908
107909     // private
107910     getContentTarget: function() {
107911         return this.inputEl;
107912     }
107913
107914     /**
107915      * @cfg {String} inputType
107916      * @hide
107917      */
107918     /**
107919      * @cfg {Boolean} disabled
107920      * @hide
107921      */
107922     /**
107923      * @cfg {Boolean} readOnly
107924      * @hide
107925      */
107926     /**
107927      * @cfg {Boolean} validateOnChange
107928      * @hide
107929      */
107930     /**
107931      * @cfg {Number} checkChangeEvents
107932      * @hide
107933      */
107934     /**
107935      * @cfg {Number} checkChangeBuffer
107936      * @hide
107937      */
107938 });
107939
107940 /**
107941  * @docauthor Jason Johnston <jason@sencha.com>
107942  *
107943  * A file upload field which has custom styling and allows control over the button text and other
107944  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
107945  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
107946  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
107947  *
107948  * Because there is no secure cross-browser way to programmatically set the value of a file input,
107949  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
107950  * a value that is browser-dependent; some have just the file name, some have a full path, some use
107951  * a fake path.
107952  *
107953  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
107954  * {@link Ext.form.Basic#hasUpload} for details.
107955  *
107956  * # Example Usage
107957  *
107958  *     @example
107959  *     Ext.create('Ext.form.Panel', {
107960  *         title: 'Upload a Photo',
107961  *         width: 400,
107962  *         bodyPadding: 10,
107963  *         frame: true,
107964  *         renderTo: Ext.getBody(),
107965  *         items: [{
107966  *             xtype: 'filefield',
107967  *             name: 'photo',
107968  *             fieldLabel: 'Photo',
107969  *             labelWidth: 50,
107970  *             msgTarget: 'side',
107971  *             allowBlank: false,
107972  *             anchor: '100%',
107973  *             buttonText: 'Select Photo...'
107974  *         }],
107975  *
107976  *         buttons: [{
107977  *             text: 'Upload',
107978  *             handler: function() {
107979  *                 var form = this.up('form').getForm();
107980  *                 if(form.isValid()){
107981  *                     form.submit({
107982  *                         url: 'photo-upload.php',
107983  *                         waitMsg: 'Uploading your photo...',
107984  *                         success: function(fp, o) {
107985  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
107986  *                         }
107987  *                     });
107988  *                 }
107989  *             }
107990  *         }]
107991  *     });
107992  */
107993 Ext.define("Ext.form.field.File", {
107994     extend: 'Ext.form.field.Text',
107995     alias: ['widget.filefield', 'widget.fileuploadfield'],
107996     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
107997     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
107998
107999     /**
108000      * @cfg {String} buttonText
108001      * The button text to display on the upload button. Note that if you supply a value for
108002      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
108003      */
108004     buttonText: 'Browse...',
108005
108006     /**
108007      * @cfg {Boolean} buttonOnly
108008      * True to display the file upload field as a button with no visible text field. If true, all
108009      * inherited Text members will still be available.
108010      */
108011     buttonOnly: false,
108012
108013     /**
108014      * @cfg {Number} buttonMargin
108015      * The number of pixels of space reserved between the button and the text field. Note that this only
108016      * applies if {@link #buttonOnly} = false.
108017      */
108018     buttonMargin: 3,
108019
108020     /**
108021      * @cfg {Object} buttonConfig
108022      * A standard {@link Ext.button.Button} config object.
108023      */
108024
108025     /**
108026      * @event change
108027      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
108028      * file selection dialog.
108029      * @param {Ext.ux.form.FileUploadField} this
108030      * @param {String} value The file value returned by the underlying file input field
108031      */
108032
108033     /**
108034      * @property {Ext.Element} fileInputEl
108035      * A reference to the invisible file input element created for this upload field. Only populated after this
108036      * component is rendered.
108037      */
108038
108039     /**
108040      * @property {Ext.button.Button} button
108041      * A reference to the trigger Button component created for this upload field. Only populated after this component is
108042      * rendered.
108043      */
108044
108045     /**
108046      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
108047      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
108048      */
108049     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
108050
108051     /**
108052      * @cfg {Boolean} readOnly
108053      * Unlike with other form fields, the readOnly config defaults to true in File field.
108054      */
108055     readOnly: true,
108056
108057     // private
108058     componentLayout: 'filefield',
108059
108060     // private
108061     onRender: function() {
108062         var me = this,
108063             inputEl;
108064
108065         me.callParent(arguments);
108066
108067         me.createButton();
108068         me.createFileInput();
108069         
108070         // we don't create the file/button til after onRender, the initial disable() is
108071         // called in the onRender of the component.
108072         if (me.disabled) {
108073             me.disableItems();
108074         }
108075
108076         inputEl = me.inputEl;
108077         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
108078         if (me.buttonOnly) {
108079             inputEl.setDisplayed(false);
108080         }
108081     },
108082
108083     /**
108084      * @private
108085      * Creates the custom trigger Button component. The fileInput will be inserted into this.
108086      */
108087     createButton: function() {
108088         var me = this;
108089         me.button = Ext.widget('button', Ext.apply({
108090             ui: me.ui,
108091             renderTo: me.bodyEl,
108092             text: me.buttonText,
108093             cls: Ext.baseCSSPrefix + 'form-file-btn',
108094             preventDefault: false,
108095             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
108096         }, me.buttonConfig));
108097     },
108098
108099     /**
108100      * @private
108101      * Creates the file input element. It is inserted into the trigger button component, made
108102      * invisible, and floated on top of the button's other content so that it will receive the
108103      * button's clicks.
108104      */
108105     createFileInput : function() {
108106         var me = this;
108107         me.fileInputEl = me.button.el.createChild({
108108             name: me.getName(),
108109             cls: Ext.baseCSSPrefix + 'form-file-input',
108110             tag: 'input',
108111             type: 'file',
108112             size: 1
108113         }).on('change', me.onFileChange, me);
108114     },
108115
108116     /**
108117      * @private Event handler fired when the user selects a file.
108118      */
108119     onFileChange: function() {
108120         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
108121         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
108122     },
108123
108124     /**
108125      * Overridden to do nothing
108126      * @hide
108127      */
108128     setValue: Ext.emptyFn,
108129
108130     reset : function(){
108131         var me = this;
108132         if (me.rendered) {
108133             me.fileInputEl.remove();
108134             me.createFileInput();
108135             me.inputEl.dom.value = '';
108136         }
108137         me.callParent();
108138     },
108139
108140     onDisable: function(){
108141         this.callParent();
108142         this.disableItems();
108143     },
108144     
108145     disableItems: function(){
108146         var file = this.fileInputEl,
108147             button = this.button;
108148              
108149         if (file) {
108150             file.dom.disabled = true;
108151         }
108152         if (button) {
108153             button.disable();
108154         }    
108155     },
108156
108157     onEnable: function(){
108158         var me = this;
108159         me.callParent();
108160         me.fileInputEl.dom.disabled = false;
108161         me.button.enable();
108162     },
108163
108164     isFileUpload: function() {
108165         return true;
108166     },
108167
108168     extractFileInput: function() {
108169         var fileInput = this.fileInputEl.dom;
108170         this.reset();
108171         return fileInput;
108172     },
108173
108174     onDestroy: function(){
108175         Ext.destroyMembers(this, 'fileInputEl', 'button');
108176         this.callParent();
108177     }
108178
108179
108180 });
108181
108182 /**
108183  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
108184  *
108185  * This creates an actual input element with type="submit" in the DOM. While its label is
108186  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
108187  * to its owner container's layout.
108188  *
108189  * Because of this, in most cases it is more convenient and less problematic to simply
108190  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
108191  * {@link Ext.form.Basic#submit submitting the form}.
108192  *
108193  * Example:
108194  *
108195  *     new Ext.form.Panel({
108196  *         title: 'My Form',
108197  *         items: [{
108198  *             xtype: 'textfield',
108199  *             fieldLabel: 'Text Field',
108200  *             name: 'text_field',
108201  *             value: 'value from text field'
108202  *         }, {
108203  *             xtype: 'hiddenfield',
108204  *             name: 'hidden_field_1',
108205  *             value: 'value from hidden field'
108206  *         }],
108207  *
108208  *         buttons: [{
108209  *             text: 'Submit',
108210  *             handler: function() {
108211  *                 this.up('form').getForm().submit({
108212  *                     params: {
108213  *                         hidden_field_2: 'value from submit call'
108214  *                     }
108215  *                 });
108216  *             }
108217  *         }]
108218  *     });
108219  *
108220  * Submitting the above form will result in three values sent to the server:
108221  *
108222  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
108223  *
108224  */
108225 Ext.define('Ext.form.field.Hidden', {
108226     extend:'Ext.form.field.Base',
108227     alias: ['widget.hiddenfield', 'widget.hidden'],
108228     alternateClassName: 'Ext.form.Hidden',
108229
108230     // private
108231     inputType : 'hidden',
108232     hideLabel: true,
108233     
108234     initComponent: function(){
108235         this.formItemCls += '-hidden';
108236         this.callParent();    
108237     },
108238     
108239     /**
108240      * @private
108241      * Override. Treat undefined and null values as equal to an empty string value.
108242      */
108243     isEqual: function(value1, value2) {
108244         return this.isEqualAsString(value1, value2);
108245     },
108246
108247     // These are all private overrides
108248     initEvents: Ext.emptyFn,
108249     setSize : Ext.emptyFn,
108250     setWidth : Ext.emptyFn,
108251     setHeight : Ext.emptyFn,
108252     setPosition : Ext.emptyFn,
108253     setPagePosition : Ext.emptyFn,
108254     markInvalid : Ext.emptyFn,
108255     clearInvalid : Ext.emptyFn
108256 });
108257
108258 /**
108259  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
108260  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
108261  *
108262  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
108263  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
108264  *
108265  *     @example
108266  *     Ext.create('Ext.picker.Color', {
108267  *         value: '993300',  // initial selected color
108268  *         renderTo: Ext.getBody(),
108269  *         listeners: {
108270  *             select: function(picker, selColor) {
108271  *                 alert(selColor);
108272  *             }
108273  *         }
108274  *     });
108275  */
108276 Ext.define('Ext.picker.Color', {
108277     extend: 'Ext.Component',
108278     requires: 'Ext.XTemplate',
108279     alias: 'widget.colorpicker',
108280     alternateClassName: 'Ext.ColorPalette',
108281
108282     /**
108283      * @cfg {String} [componentCls='x-color-picker']
108284      * The CSS class to apply to the containing element.
108285      */
108286     componentCls : Ext.baseCSSPrefix + 'color-picker',
108287
108288     /**
108289      * @cfg {String} [selectedCls='x-color-picker-selected']
108290      * The CSS class to apply to the selected element
108291      */
108292     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
108293
108294     /**
108295      * @cfg {String} value
108296      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
108297      * codes are case-sensitive.
108298      */
108299     value : null,
108300
108301     /**
108302      * @cfg {String} clickEvent
108303      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
108304      */
108305     clickEvent :'click',
108306
108307     /**
108308      * @cfg {Boolean} allowReselect
108309      * If set to true then reselecting a color that is already selected fires the {@link #select} event
108310      */
108311     allowReselect : false,
108312
108313     /**
108314      * @property {String[]} colors
108315      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
108316      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
108317      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
108318      * width setting until the box is symmetrical.
108319      *
108320      * You can override individual colors if needed:
108321      *
108322      *     var cp = new Ext.picker.Color();
108323      *     cp.colors[0] = 'FF0000';  // change the first box to red
108324      *
108325      * Or you can provide a custom array of your own for complete control:
108326      *
108327      *     var cp = new Ext.picker.Color();
108328      *     cp.colors = ['000000', '993300', '333300'];
108329      */
108330     colors : [
108331         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
108332         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
108333         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
108334         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
108335         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
108336     ],
108337
108338     /**
108339      * @cfg {Function} handler
108340      * A function that will handle the select event of this picker. The handler is passed the following parameters:
108341      *
108342      * - `picker` : ColorPicker
108343      *
108344      *   The {@link Ext.picker.Color picker}.
108345      *
108346      * - `color` : String
108347      *
108348      *   The 6-digit color hex code (without the # symbol).
108349      */
108350
108351     /**
108352      * @cfg {Object} scope
108353      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
108354      * Color picker instance.
108355      */
108356
108357     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
108358     
108359     renderTpl: [
108360         '<tpl for="colors">',
108361             '<a href="#" class="color-{.}" hidefocus="on">',
108362                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
108363             '</a>',
108364         '</tpl>'
108365     ],
108366
108367     // private
108368     initComponent : function(){
108369         var me = this;
108370
108371         me.callParent(arguments);
108372         me.addEvents(
108373             /**
108374              * @event select
108375              * Fires when a color is selected
108376              * @param {Ext.picker.Color} this
108377              * @param {String} color The 6-digit color hex code (without the # symbol)
108378              */
108379             'select'
108380         );
108381
108382         if (me.handler) {
108383             me.on('select', me.handler, me.scope, true);
108384         }
108385     },
108386
108387
108388     // private
108389     onRender : function(container, position){
108390         var me = this,
108391             clickEvent = me.clickEvent;
108392
108393         Ext.apply(me.renderData, {
108394             itemCls: me.itemCls,
108395             colors: me.colors
108396         });
108397         me.callParent(arguments);
108398
108399         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
108400         // always stop following the anchors
108401         if(clickEvent != 'click'){
108402             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
108403         }
108404     },
108405
108406     // private
108407     afterRender : function(){
108408         var me = this,
108409             value;
108410
108411         me.callParent(arguments);
108412         if (me.value) {
108413             value = me.value;
108414             me.value = null;
108415             me.select(value, true);
108416         }
108417     },
108418
108419     // private
108420     handleClick : function(event, target){
108421         var me = this,
108422             color;
108423
108424         event.stopEvent();
108425         if (!me.disabled) {
108426             color = target.className.match(me.colorRe)[1];
108427             me.select(color.toUpperCase());
108428         }
108429     },
108430
108431     /**
108432      * Selects the specified color in the picker (fires the {@link #select} event)
108433      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
108434      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
108435      */
108436     select : function(color, suppressEvent){
108437
108438         var me = this,
108439             selectedCls = me.selectedCls,
108440             value = me.value,
108441             el;
108442
108443         color = color.replace('#', '');
108444         if (!me.rendered) {
108445             me.value = color;
108446             return;
108447         }
108448
108449
108450         if (color != value || me.allowReselect) {
108451             el = me.el;
108452
108453             if (me.value) {
108454                 el.down('a.color-' + value).removeCls(selectedCls);
108455             }
108456             el.down('a.color-' + color).addCls(selectedCls);
108457             me.value = color;
108458             if (suppressEvent !== true) {
108459                 me.fireEvent('select', me, color);
108460             }
108461         }
108462     },
108463
108464     /**
108465      * Get the currently selected color value.
108466      * @return {String} value The selected value. Null if nothing is selected.
108467      */
108468     getValue: function(){
108469         return this.value || null;
108470     }
108471 });
108472
108473 /**
108474  * @private
108475  * @class Ext.layout.component.field.HtmlEditor
108476  * @extends Ext.layout.component.field.Field
108477  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
108478  * @private
108479  */
108480
108481 Ext.define('Ext.layout.component.field.HtmlEditor', {
108482     extend: 'Ext.layout.component.field.Field',
108483     alias: ['layout.htmleditor'],
108484
108485     type: 'htmleditor',
108486
108487     sizeBodyContents: function(width, height) {
108488         var me = this,
108489             owner = me.owner,
108490             bodyEl = owner.bodyEl,
108491             toolbar = owner.getToolbar(),
108492             textarea = owner.textareaEl,
108493             iframe = owner.iframeEl,
108494             editorHeight;
108495
108496         if (Ext.isNumber(width)) {
108497             width -= bodyEl.getFrameWidth('lr');
108498         }
108499         toolbar.setWidth(width);
108500         textarea.setWidth(width);
108501         iframe.setWidth(width);
108502
108503         // If fixed height, subtract toolbar height from the input area height
108504         if (Ext.isNumber(height)) {
108505             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
108506             textarea.setHeight(editorHeight);
108507             iframe.setHeight(editorHeight);
108508         }
108509     }
108510 });
108511 /**
108512  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
108513  * automatically hidden when needed. These are noted in the config options where appropriate.
108514  *
108515  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
108516  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
108517  * {@link Ext.tip.QuickTipManager#init initialized}.
108518  *
108519  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
108520  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
108521  * default iframe reloading bugs.
108522  *
108523  * # Example usage
108524  *
108525  * Simple example rendered with default options:
108526  *
108527  *     @example
108528  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108529  *     Ext.create('Ext.form.HtmlEditor', {
108530  *         width: 580,
108531  *         height: 250,
108532  *         renderTo: Ext.getBody()
108533  *     });
108534  *
108535  * Passed via xtype into a container and with custom options:
108536  *
108537  *     @example
108538  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108539  *     new Ext.panel.Panel({
108540  *         title: 'HTML Editor',
108541  *         renderTo: Ext.getBody(),
108542  *         width: 550,
108543  *         height: 250,
108544  *         frame: true,
108545  *         layout: 'fit',
108546  *         items: {
108547  *             xtype: 'htmleditor',
108548  *             enableColors: false,
108549  *             enableAlignments: false
108550  *         }
108551  *     });
108552  */
108553 Ext.define('Ext.form.field.HtmlEditor', {
108554     extend:'Ext.Component',
108555     mixins: {
108556         labelable: 'Ext.form.Labelable',
108557         field: 'Ext.form.field.Field'
108558     },
108559     alias: 'widget.htmleditor',
108560     alternateClassName: 'Ext.form.HtmlEditor',
108561     requires: [
108562         'Ext.tip.QuickTipManager',
108563         'Ext.picker.Color',
108564         'Ext.toolbar.Item',
108565         'Ext.toolbar.Toolbar',
108566         'Ext.util.Format',
108567         'Ext.layout.component.field.HtmlEditor'
108568     ],
108569
108570     fieldSubTpl: [
108571         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
108572         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
108573             'style="{size}" autocomplete="off"></textarea>',
108574         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
108575         {
108576             compiled: true,
108577             disableFormats: true
108578         }
108579     ],
108580
108581     /**
108582      * @cfg {Boolean} enableFormat
108583      * Enable the bold, italic and underline buttons
108584      */
108585     enableFormat : true,
108586     /**
108587      * @cfg {Boolean} enableFontSize
108588      * Enable the increase/decrease font size buttons
108589      */
108590     enableFontSize : true,
108591     /**
108592      * @cfg {Boolean} enableColors
108593      * Enable the fore/highlight color buttons
108594      */
108595     enableColors : true,
108596     /**
108597      * @cfg {Boolean} enableAlignments
108598      * Enable the left, center, right alignment buttons
108599      */
108600     enableAlignments : true,
108601     /**
108602      * @cfg {Boolean} enableLists
108603      * Enable the bullet and numbered list buttons. Not available in Safari.
108604      */
108605     enableLists : true,
108606     /**
108607      * @cfg {Boolean} enableSourceEdit
108608      * Enable the switch to source edit button. Not available in Safari.
108609      */
108610     enableSourceEdit : true,
108611     /**
108612      * @cfg {Boolean} enableLinks
108613      * Enable the create link button. Not available in Safari.
108614      */
108615     enableLinks : true,
108616     /**
108617      * @cfg {Boolean} enableFont
108618      * Enable font selection. Not available in Safari.
108619      */
108620     enableFont : true,
108621     /**
108622      * @cfg {String} createLinkText
108623      * The default text for the create link prompt
108624      */
108625     createLinkText : 'Please enter the URL for the link:',
108626     /**
108627      * @cfg {String} [defaultLinkValue='http://']
108628      * The default value for the create link prompt
108629      */
108630     defaultLinkValue : 'http:/'+'/',
108631     /**
108632      * @cfg {String[]} fontFamilies
108633      * An array of available font families
108634      */
108635     fontFamilies : [
108636         'Arial',
108637         'Courier New',
108638         'Tahoma',
108639         'Times New Roman',
108640         'Verdana'
108641     ],
108642     defaultFont: 'tahoma',
108643     /**
108644      * @cfg {String} defaultValue
108645      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
108646      * and IE6, â€‹(Zero-width space) in all other browsers).
108647      */
108648     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
108649
108650     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
108651
108652     componentLayout: 'htmleditor',
108653
108654     // private properties
108655     initialized : false,
108656     activated : false,
108657     sourceEditMode : false,
108658     iframePad:3,
108659     hideMode:'offsets',
108660
108661     maskOnDisable: true,
108662
108663     // private
108664     initComponent : function(){
108665         var me = this;
108666
108667         me.addEvents(
108668             /**
108669              * @event initialize
108670              * Fires when the editor is fully initialized (including the iframe)
108671              * @param {Ext.form.field.HtmlEditor} this
108672              */
108673             'initialize',
108674             /**
108675              * @event activate
108676              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
108677              * @param {Ext.form.field.HtmlEditor} this
108678              */
108679             'activate',
108680              /**
108681              * @event beforesync
108682              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
108683              * sync.
108684              * @param {Ext.form.field.HtmlEditor} this
108685              * @param {String} html
108686              */
108687             'beforesync',
108688              /**
108689              * @event beforepush
108690              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
108691              * push.
108692              * @param {Ext.form.field.HtmlEditor} this
108693              * @param {String} html
108694              */
108695             'beforepush',
108696              /**
108697              * @event sync
108698              * Fires when the textarea is updated with content from the editor iframe.
108699              * @param {Ext.form.field.HtmlEditor} this
108700              * @param {String} html
108701              */
108702             'sync',
108703              /**
108704              * @event push
108705              * Fires when the iframe editor is updated with content from the textarea.
108706              * @param {Ext.form.field.HtmlEditor} this
108707              * @param {String} html
108708              */
108709             'push',
108710              /**
108711              * @event editmodechange
108712              * Fires when the editor switches edit modes
108713              * @param {Ext.form.field.HtmlEditor} this
108714              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
108715              */
108716             'editmodechange'
108717         );
108718
108719         me.callParent(arguments);
108720
108721         // Init mixins
108722         me.initLabelable();
108723         me.initField();
108724     },
108725
108726     /**
108727      * Called when the editor creates its toolbar. Override this method if you need to
108728      * add custom toolbar buttons.
108729      * @param {Ext.form.field.HtmlEditor} editor
108730      * @protected
108731      */
108732     createToolbar : function(editor){
108733         var me = this,
108734             items = [],
108735             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
108736             baseCSSPrefix = Ext.baseCSSPrefix,
108737             fontSelectItem, toolbar, undef;
108738
108739         function btn(id, toggle, handler){
108740             return {
108741                 itemId : id,
108742                 cls : baseCSSPrefix + 'btn-icon',
108743                 iconCls: baseCSSPrefix + 'edit-'+id,
108744                 enableToggle:toggle !== false,
108745                 scope: editor,
108746                 handler:handler||editor.relayBtnCmd,
108747                 clickEvent:'mousedown',
108748                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
108749                 overflowText: editor.buttonTips[id].title || undef,
108750                 tabIndex:-1
108751             };
108752         }
108753
108754
108755         if (me.enableFont && !Ext.isSafari2) {
108756             fontSelectItem = Ext.widget('component', {
108757                 renderTpl: [
108758                     '<select id="{id}-selectEl" class="{cls}">',
108759                         '<tpl for="fonts">',
108760                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
108761                         '</tpl>',
108762                     '</select>'
108763                 ],
108764                 renderData: {
108765                     cls: baseCSSPrefix + 'font-select',
108766                     fonts: me.fontFamilies,
108767                     defaultFont: me.defaultFont
108768                 },
108769                 childEls: ['selectEl'],
108770                 onDisable: function() {
108771                     var selectEl = this.selectEl;
108772                     if (selectEl) {
108773                         selectEl.dom.disabled = true;
108774                     }
108775                     Ext.Component.superclass.onDisable.apply(this, arguments);
108776                 },
108777                 onEnable: function() {
108778                     var selectEl = this.selectEl;
108779                     if (selectEl) {
108780                         selectEl.dom.disabled = false;
108781                     }
108782                     Ext.Component.superclass.onEnable.apply(this, arguments);
108783                 }
108784             });
108785
108786             items.push(
108787                 fontSelectItem,
108788                 '-'
108789             );
108790         }
108791
108792         if (me.enableFormat) {
108793             items.push(
108794                 btn('bold'),
108795                 btn('italic'),
108796                 btn('underline')
108797             );
108798         }
108799
108800         if (me.enableFontSize) {
108801             items.push(
108802                 '-',
108803                 btn('increasefontsize', false, me.adjustFont),
108804                 btn('decreasefontsize', false, me.adjustFont)
108805             );
108806         }
108807
108808         if (me.enableColors) {
108809             items.push(
108810                 '-', {
108811                     itemId: 'forecolor',
108812                     cls: baseCSSPrefix + 'btn-icon',
108813                     iconCls: baseCSSPrefix + 'edit-forecolor',
108814                     overflowText: editor.buttonTips.forecolor.title,
108815                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
108816                     tabIndex:-1,
108817                     menu : Ext.widget('menu', {
108818                         plain: true,
108819                         items: [{
108820                             xtype: 'colorpicker',
108821                             allowReselect: true,
108822                             focus: Ext.emptyFn,
108823                             value: '000000',
108824                             plain: true,
108825                             clickEvent: 'mousedown',
108826                             handler: function(cp, color) {
108827                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108828                                 me.deferFocus();
108829                                 this.up('menu').hide();
108830                             }
108831                         }]
108832                     })
108833                 }, {
108834                     itemId: 'backcolor',
108835                     cls: baseCSSPrefix + 'btn-icon',
108836                     iconCls: baseCSSPrefix + 'edit-backcolor',
108837                     overflowText: editor.buttonTips.backcolor.title,
108838                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
108839                     tabIndex:-1,
108840                     menu : Ext.widget('menu', {
108841                         plain: true,
108842                         items: [{
108843                             xtype: 'colorpicker',
108844                             focus: Ext.emptyFn,
108845                             value: 'FFFFFF',
108846                             plain: true,
108847                             allowReselect: true,
108848                             clickEvent: 'mousedown',
108849                             handler: function(cp, color) {
108850                                 if (Ext.isGecko) {
108851                                     me.execCmd('useCSS', false);
108852                                     me.execCmd('hilitecolor', color);
108853                                     me.execCmd('useCSS', true);
108854                                     me.deferFocus();
108855                                 } else {
108856                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108857                                     me.deferFocus();
108858                                 }
108859                                 this.up('menu').hide();
108860                             }
108861                         }]
108862                     })
108863                 }
108864             );
108865         }
108866
108867         if (me.enableAlignments) {
108868             items.push(
108869                 '-',
108870                 btn('justifyleft'),
108871                 btn('justifycenter'),
108872                 btn('justifyright')
108873             );
108874         }
108875
108876         if (!Ext.isSafari2) {
108877             if (me.enableLinks) {
108878                 items.push(
108879                     '-',
108880                     btn('createlink', false, me.createLink)
108881                 );
108882             }
108883
108884             if (me.enableLists) {
108885                 items.push(
108886                     '-',
108887                     btn('insertorderedlist'),
108888                     btn('insertunorderedlist')
108889                 );
108890             }
108891             if (me.enableSourceEdit) {
108892                 items.push(
108893                     '-',
108894                     btn('sourceedit', true, function(btn){
108895                         me.toggleSourceEdit(!me.sourceEditMode);
108896                     })
108897                 );
108898             }
108899         }
108900
108901         // build the toolbar
108902         toolbar = Ext.widget('toolbar', {
108903             renderTo: me.toolbarWrap,
108904             enableOverflow: true,
108905             items: items
108906         });
108907
108908         if (fontSelectItem) {
108909             me.fontSelect = fontSelectItem.selectEl;
108910
108911             me.mon(me.fontSelect, 'change', function(){
108912                 me.relayCmd('fontname', me.fontSelect.dom.value);
108913                 me.deferFocus();
108914             });
108915         }
108916
108917         // stop form submits
108918         me.mon(toolbar.el, 'click', function(e){
108919             e.preventDefault();
108920         });
108921
108922         me.toolbar = toolbar;
108923     },
108924
108925     onDisable: function() {
108926         this.bodyEl.mask();
108927         this.callParent(arguments);
108928     },
108929
108930     onEnable: function() {
108931         this.bodyEl.unmask();
108932         this.callParent(arguments);
108933     },
108934
108935     /**
108936      * Sets the read only state of this field.
108937      * @param {Boolean} readOnly Whether the field should be read only.
108938      */
108939     setReadOnly: function(readOnly) {
108940         var me = this,
108941             textareaEl = me.textareaEl,
108942             iframeEl = me.iframeEl,
108943             body;
108944
108945         me.readOnly = readOnly;
108946
108947         if (textareaEl) {
108948             textareaEl.dom.readOnly = readOnly;
108949         }
108950
108951         if (me.initialized) {
108952             body = me.getEditorBody();
108953             if (Ext.isIE) {
108954                 // Hide the iframe while setting contentEditable so it doesn't grab focus
108955                 iframeEl.setDisplayed(false);
108956                 body.contentEditable = !readOnly;
108957                 iframeEl.setDisplayed(true);
108958             } else {
108959                 me.setDesignMode(!readOnly);
108960             }
108961             if (body) {
108962                 body.style.cursor = readOnly ? 'default' : 'text';
108963             }
108964             me.disableItems(readOnly);
108965         }
108966     },
108967
108968     /**
108969      * Called when the editor initializes the iframe with HTML contents. Override this method if you
108970      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
108971      *
108972      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
108973      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
108974      * Developer Tools to manually set the document mode, that will take precedence and override what this
108975      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
108976      * @protected
108977      */
108978     getDocMarkup: function() {
108979         var me = this,
108980             h = me.iframeEl.getHeight() - me.iframePad * 2;
108981         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);
108982     },
108983
108984     // private
108985     getEditorBody: function() {
108986         var doc = this.getDoc();
108987         return doc.body || doc.documentElement;
108988     },
108989
108990     // private
108991     getDoc: function() {
108992         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
108993     },
108994
108995     // private
108996     getWin: function() {
108997         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
108998     },
108999
109000     // private
109001     onRender: function() {
109002         var me = this;
109003
109004         me.onLabelableRender();
109005
109006         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
109007
109008         me.callParent(arguments);
109009
109010         me.textareaEl.dom.value = me.value || '';
109011
109012         // Start polling for when the iframe document is ready to be manipulated
109013         me.monitorTask = Ext.TaskManager.start({
109014             run: me.checkDesignMode,
109015             scope: me,
109016             interval:100
109017         });
109018
109019         me.createToolbar(me);
109020         me.disableItems(true);
109021     },
109022
109023     initRenderTpl: function() {
109024         var me = this;
109025         if (!me.hasOwnProperty('renderTpl')) {
109026             me.renderTpl = me.getTpl('labelableRenderTpl');
109027         }
109028         return me.callParent();
109029     },
109030
109031     initRenderData: function() {
109032         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
109033     },
109034
109035     getSubTplData: function() {
109036         var cssPrefix = Ext.baseCSSPrefix;
109037         return {
109038             cmpId: this.id,
109039             id: this.getInputId(),
109040             toolbarWrapCls: cssPrefix + 'html-editor-tb',
109041             textareaCls: cssPrefix + 'hidden',
109042             iframeName: Ext.id(),
109043             iframeSrc: Ext.SSL_SECURE_URL,
109044             size: 'height:100px;'
109045         };
109046     },
109047
109048     getSubTplMarkup: function() {
109049         var data = this.getSubTplData();
109050         return this.getTpl('fieldSubTpl').apply(data);
109051     },
109052
109053     getBodyNaturalWidth: function() {
109054         return 565;
109055     },
109056
109057     initFrameDoc: function() {
109058         var me = this,
109059             doc, task;
109060
109061         Ext.TaskManager.stop(me.monitorTask);
109062
109063         doc = me.getDoc();
109064         me.win = me.getWin();
109065
109066         doc.open();
109067         doc.write(me.getDocMarkup());
109068         doc.close();
109069
109070         task = { // must defer to wait for browser to be ready
109071             run: function() {
109072                 var doc = me.getDoc();
109073                 if (doc.body || doc.readyState === 'complete') {
109074                     Ext.TaskManager.stop(task);
109075                     me.setDesignMode(true);
109076                     Ext.defer(me.initEditor, 10, me);
109077                 }
109078             },
109079             interval : 10,
109080             duration:10000,
109081             scope: me
109082         };
109083         Ext.TaskManager.start(task);
109084     },
109085
109086     checkDesignMode: function() {
109087         var me = this,
109088             doc = me.getDoc();
109089         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
109090             me.initFrameDoc();
109091         }
109092     },
109093
109094     /**
109095      * @private
109096      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
109097      */
109098     setDesignMode: function(mode) {
109099         var me = this,
109100             doc = me.getDoc();
109101         if (doc) {
109102             if (me.readOnly) {
109103                 mode = false;
109104             }
109105             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
109106         }
109107     },
109108
109109     // private
109110     getDesignMode: function() {
109111         var doc = this.getDoc();
109112         return !doc ? '' : String(doc.designMode).toLowerCase();
109113     },
109114
109115     disableItems: function(disabled) {
109116         this.getToolbar().items.each(function(item){
109117             if(item.getItemId() !== 'sourceedit'){
109118                 item.setDisabled(disabled);
109119             }
109120         });
109121     },
109122
109123     /**
109124      * Toggles the editor between standard and source edit mode.
109125      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
109126      */
109127     toggleSourceEdit: function(sourceEditMode) {
109128         var me = this,
109129             iframe = me.iframeEl,
109130             textarea = me.textareaEl,
109131             hiddenCls = Ext.baseCSSPrefix + 'hidden',
109132             btn = me.getToolbar().getComponent('sourceedit');
109133
109134         if (!Ext.isBoolean(sourceEditMode)) {
109135             sourceEditMode = !me.sourceEditMode;
109136         }
109137         me.sourceEditMode = sourceEditMode;
109138
109139         if (btn.pressed !== sourceEditMode) {
109140             btn.toggle(sourceEditMode);
109141         }
109142         if (sourceEditMode) {
109143             me.disableItems(true);
109144             me.syncValue();
109145             iframe.addCls(hiddenCls);
109146             textarea.removeCls(hiddenCls);
109147             textarea.dom.removeAttribute('tabIndex');
109148             textarea.focus();
109149         }
109150         else {
109151             if (me.initialized) {
109152                 me.disableItems(me.readOnly);
109153             }
109154             me.pushValue();
109155             iframe.removeCls(hiddenCls);
109156             textarea.addCls(hiddenCls);
109157             textarea.dom.setAttribute('tabIndex', -1);
109158             me.deferFocus();
109159         }
109160         me.fireEvent('editmodechange', me, sourceEditMode);
109161         me.doComponentLayout();
109162     },
109163
109164     // private used internally
109165     createLink : function() {
109166         var url = prompt(this.createLinkText, this.defaultLinkValue);
109167         if (url && url !== 'http:/'+'/') {
109168             this.relayCmd('createlink', url);
109169         }
109170     },
109171
109172     clearInvalid: Ext.emptyFn,
109173
109174     // docs inherit from Field
109175     setValue: function(value) {
109176         var me = this,
109177             textarea = me.textareaEl;
109178         me.mixins.field.setValue.call(me, value);
109179         if (value === null || value === undefined) {
109180             value = '';
109181         }
109182         if (textarea) {
109183             textarea.dom.value = value;
109184         }
109185         me.pushValue();
109186         return this;
109187     },
109188
109189     /**
109190      * If you need/want custom HTML cleanup, this is the method you should override.
109191      * @param {String} html The HTML to be cleaned
109192      * @return {String} The cleaned HTML
109193      * @protected
109194      */
109195     cleanHtml: function(html) {
109196         html = String(html);
109197         if (Ext.isWebKit) { // strip safari nonsense
109198             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
109199         }
109200
109201         /*
109202          * Neat little hack. Strips out all the non-digit characters from the default
109203          * value and compares it to the character code of the first character in the string
109204          * because it can cause encoding issues when posted to the server.
109205          */
109206         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
109207             html = html.substring(1);
109208         }
109209         return html;
109210     },
109211
109212     /**
109213      * Syncs the contents of the editor iframe with the textarea.
109214      * @protected
109215      */
109216     syncValue : function(){
109217         var me = this,
109218             body, html, bodyStyle, match;
109219         if (me.initialized) {
109220             body = me.getEditorBody();
109221             html = body.innerHTML;
109222             if (Ext.isWebKit) {
109223                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
109224                 match = bodyStyle.match(/text-align:(.*?);/i);
109225                 if (match && match[1]) {
109226                     html = '<div style="' + match[0] + '">' + html + '</div>';
109227                 }
109228             }
109229             html = me.cleanHtml(html);
109230             if (me.fireEvent('beforesync', me, html) !== false) {
109231                 me.textareaEl.dom.value = html;
109232                 me.fireEvent('sync', me, html);
109233             }
109234         }
109235     },
109236
109237     //docs inherit from Field
109238     getValue : function() {
109239         var me = this,
109240             value;
109241         if (!me.sourceEditMode) {
109242             me.syncValue();
109243         }
109244         value = me.rendered ? me.textareaEl.dom.value : me.value;
109245         me.value = value;
109246         return value;
109247     },
109248
109249     /**
109250      * Pushes the value of the textarea into the iframe editor.
109251      * @protected
109252      */
109253     pushValue: function() {
109254         var me = this,
109255             v;
109256         if(me.initialized){
109257             v = me.textareaEl.dom.value || '';
109258             if (!me.activated && v.length < 1) {
109259                 v = me.defaultValue;
109260             }
109261             if (me.fireEvent('beforepush', me, v) !== false) {
109262                 me.getEditorBody().innerHTML = v;
109263                 if (Ext.isGecko) {
109264                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
109265                     me.setDesignMode(false);  //toggle off first
109266                     me.setDesignMode(true);
109267                 }
109268                 me.fireEvent('push', me, v);
109269             }
109270         }
109271     },
109272
109273     // private
109274     deferFocus : function(){
109275          this.focus(false, true);
109276     },
109277
109278     getFocusEl: function() {
109279         var me = this,
109280             win = me.win;
109281         return win && !me.sourceEditMode ? win : me.textareaEl;
109282     },
109283
109284     // private
109285     initEditor : function(){
109286         //Destroying the component during/before initEditor can cause issues.
109287         try {
109288             var me = this,
109289                 dbody = me.getEditorBody(),
109290                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
109291                 doc,
109292                 fn;
109293
109294             ss['background-attachment'] = 'fixed'; // w3c
109295             dbody.bgProperties = 'fixed'; // ie
109296
109297             Ext.DomHelper.applyStyles(dbody, ss);
109298
109299             doc = me.getDoc();
109300
109301             if (doc) {
109302                 try {
109303                     Ext.EventManager.removeAll(doc);
109304                 } catch(e) {}
109305             }
109306
109307             /*
109308              * We need to use createDelegate here, because when using buffer, the delayed task is added
109309              * as a property to the function. When the listener is removed, the task is deleted from the function.
109310              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
109311              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
109312              */
109313             fn = Ext.Function.bind(me.onEditorEvent, me);
109314             Ext.EventManager.on(doc, {
109315                 mousedown: fn,
109316                 dblclick: fn,
109317                 click: fn,
109318                 keyup: fn,
109319                 buffer:100
109320             });
109321
109322             // These events need to be relayed from the inner document (where they stop
109323             // bubbling) up to the outer document. This has to be done at the DOM level so
109324             // the event reaches listeners on elements like the document body. The effected
109325             // mechanisms that depend on this bubbling behavior are listed to the right
109326             // of the event.
109327             fn = me.onRelayedEvent;
109328             Ext.EventManager.on(doc, {
109329                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
109330                 mousemove: fn, // window resize drag detection
109331                 mouseup: fn,   // window resize termination
109332                 click: fn,     // not sure, but just to be safe
109333                 dblclick: fn,  // not sure again
109334                 scope: me
109335             });
109336
109337             if (Ext.isGecko) {
109338                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
109339             }
109340             if (me.fixKeys) {
109341                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
109342             }
109343
109344             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
109345             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
109346             doc.editorInitialized = true;
109347
109348             me.initialized = true;
109349             me.pushValue();
109350             me.setReadOnly(me.readOnly);
109351             me.fireEvent('initialize', me);
109352         } catch(ex) {
109353             // ignore (why?)
109354         }
109355     },
109356
109357     // private
109358     beforeDestroy : function(){
109359         var me = this,
109360             monitorTask = me.monitorTask,
109361             doc, prop;
109362
109363         if (monitorTask) {
109364             Ext.TaskManager.stop(monitorTask);
109365         }
109366         if (me.rendered) {
109367             try {
109368                 doc = me.getDoc();
109369                 if (doc) {
109370                     Ext.EventManager.removeAll(doc);
109371                     for (prop in doc) {
109372                         if (doc.hasOwnProperty(prop)) {
109373                             delete doc[prop];
109374                         }
109375                     }
109376                 }
109377             } catch(e) {
109378                 // ignore (why?)
109379             }
109380             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
109381         }
109382         me.callParent();
109383     },
109384
109385     // private
109386     onRelayedEvent: function (event) {
109387         // relay event from the iframe's document to the document that owns the iframe...
109388
109389         var iframeEl = this.iframeEl,
109390             iframeXY = iframeEl.getXY(),
109391             eventXY = event.getXY();
109392
109393         // the event from the inner document has XY relative to that document's origin,
109394         // so adjust it to use the origin of the iframe in the outer document:
109395         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
109396
109397         event.injectEvent(iframeEl); // blame the iframe for the event...
109398
109399         event.xy = eventXY; // restore the original XY (just for safety)
109400     },
109401
109402     // private
109403     onFirstFocus : function(){
109404         var me = this,
109405             selection, range;
109406         me.activated = true;
109407         me.disableItems(me.readOnly);
109408         if (Ext.isGecko) { // prevent silly gecko errors
109409             me.win.focus();
109410             selection = me.win.getSelection();
109411             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
109412                 range = selection.getRangeAt(0);
109413                 range.selectNodeContents(me.getEditorBody());
109414                 range.collapse(true);
109415                 me.deferFocus();
109416             }
109417             try {
109418                 me.execCmd('useCSS', true);
109419                 me.execCmd('styleWithCSS', false);
109420             } catch(e) {
109421                 // ignore (why?)
109422             }
109423         }
109424         me.fireEvent('activate', me);
109425     },
109426
109427     // private
109428     adjustFont: function(btn) {
109429         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
109430             size = this.getDoc().queryCommandValue('FontSize') || '2',
109431             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
109432             isSafari;
109433         size = parseInt(size, 10);
109434         if (isPxSize) {
109435             // Safari 3 values
109436             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
109437             if (size <= 10) {
109438                 size = 1 + adjust;
109439             }
109440             else if (size <= 13) {
109441                 size = 2 + adjust;
109442             }
109443             else if (size <= 16) {
109444                 size = 3 + adjust;
109445             }
109446             else if (size <= 18) {
109447                 size = 4 + adjust;
109448             }
109449             else if (size <= 24) {
109450                 size = 5 + adjust;
109451             }
109452             else {
109453                 size = 6 + adjust;
109454             }
109455             size = Ext.Number.constrain(size, 1, 6);
109456         } else {
109457             isSafari = Ext.isSafari;
109458             if (isSafari) { // safari
109459                 adjust *= 2;
109460             }
109461             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
109462         }
109463         this.execCmd('FontSize', size);
109464     },
109465
109466     // private
109467     onEditorEvent: function(e) {
109468         this.updateToolbar();
109469     },
109470
109471     /**
109472      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
109473      * @protected
109474      */
109475     updateToolbar: function() {
109476         var me = this,
109477             btns, doc, name, fontSelect;
109478
109479         if (me.readOnly) {
109480             return;
109481         }
109482
109483         if (!me.activated) {
109484             me.onFirstFocus();
109485             return;
109486         }
109487
109488         btns = me.getToolbar().items.map;
109489         doc = me.getDoc();
109490
109491         if (me.enableFont && !Ext.isSafari2) {
109492             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
109493             fontSelect = me.fontSelect.dom;
109494             if (name !== fontSelect.value) {
109495                 fontSelect.value = name;
109496             }
109497         }
109498
109499         function updateButtons() {
109500             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
109501                 btns[name].toggle(doc.queryCommandState(name));
109502             });
109503         }
109504         if(me.enableFormat){
109505             updateButtons('bold', 'italic', 'underline');
109506         }
109507         if(me.enableAlignments){
109508             updateButtons('justifyleft', 'justifycenter', 'justifyright');
109509         }
109510         if(!Ext.isSafari2 && me.enableLists){
109511             updateButtons('insertorderedlist', 'insertunorderedlist');
109512         }
109513
109514         Ext.menu.Manager.hideAll();
109515
109516         me.syncValue();
109517     },
109518
109519     // private
109520     relayBtnCmd: function(btn) {
109521         this.relayCmd(btn.getItemId());
109522     },
109523
109524     /**
109525      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
109526      * **This should only be called after the editor is initialized.**
109527      * @param {String} cmd The Midas command
109528      * @param {String/Boolean} [value=null] The value to pass to the command
109529      */
109530     relayCmd: function(cmd, value) {
109531         Ext.defer(function() {
109532             var me = this;
109533             me.focus();
109534             me.execCmd(cmd, value);
109535             me.updateToolbar();
109536         }, 10, this);
109537     },
109538
109539     /**
109540      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
109541      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
109542      * @param {String} cmd The Midas command
109543      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
109544      */
109545     execCmd : function(cmd, value){
109546         var me = this,
109547             doc = me.getDoc(),
109548             undef;
109549         doc.execCommand(cmd, false, value === undef ? null : value);
109550         me.syncValue();
109551     },
109552
109553     // private
109554     applyCommand : function(e){
109555         if (e.ctrlKey) {
109556             var me = this,
109557                 c = e.getCharCode(), cmd;
109558             if (c > 0) {
109559                 c = String.fromCharCode(c);
109560                 switch (c) {
109561                     case 'b':
109562                         cmd = 'bold';
109563                     break;
109564                     case 'i':
109565                         cmd = 'italic';
109566                     break;
109567                     case 'u':
109568                         cmd = 'underline';
109569                     break;
109570                 }
109571                 if (cmd) {
109572                     me.win.focus();
109573                     me.execCmd(cmd);
109574                     me.deferFocus();
109575                     e.preventDefault();
109576                 }
109577             }
109578         }
109579     },
109580
109581     /**
109582      * Inserts the passed text at the current cursor position.
109583      * Note: the editor must be initialized and activated to insert text.
109584      * @param {String} text
109585      */
109586     insertAtCursor : function(text){
109587         var me = this,
109588             range;
109589
109590         if (me.activated) {
109591             me.win.focus();
109592             if (Ext.isIE) {
109593                 range = me.getDoc().selection.createRange();
109594                 if (range) {
109595                     range.pasteHTML(text);
109596                     me.syncValue();
109597                     me.deferFocus();
109598                 }
109599             }else{
109600                 me.execCmd('InsertHTML', text);
109601                 me.deferFocus();
109602             }
109603         }
109604     },
109605
109606     // private
109607     fixKeys: function() { // load time branching for fastest keydown performance
109608         if (Ext.isIE) {
109609             return function(e){
109610                 var me = this,
109611                     k = e.getKey(),
109612                     doc = me.getDoc(),
109613                     range, target;
109614                 if (k === e.TAB) {
109615                     e.stopEvent();
109616                     range = doc.selection.createRange();
109617                     if(range){
109618                         range.collapse(true);
109619                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
109620                         me.deferFocus();
109621                     }
109622                 }
109623                 else if (k === e.ENTER) {
109624                     range = doc.selection.createRange();
109625                     if (range) {
109626                         target = range.parentElement();
109627                         if(!target || target.tagName.toLowerCase() !== 'li'){
109628                             e.stopEvent();
109629                             range.pasteHTML('<br />');
109630                             range.collapse(false);
109631                             range.select();
109632                         }
109633                     }
109634                 }
109635             };
109636         }
109637
109638         if (Ext.isOpera) {
109639             return function(e){
109640                 var me = this;
109641                 if (e.getKey() === e.TAB) {
109642                     e.stopEvent();
109643                     me.win.focus();
109644                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
109645                     me.deferFocus();
109646                 }
109647             };
109648         }
109649
109650         if (Ext.isWebKit) {
109651             return function(e){
109652                 var me = this,
109653                     k = e.getKey();
109654                 if (k === e.TAB) {
109655                     e.stopEvent();
109656                     me.execCmd('InsertText','\t');
109657                     me.deferFocus();
109658                 }
109659                 else if (k === e.ENTER) {
109660                     e.stopEvent();
109661                     me.execCmd('InsertHtml','<br /><br />');
109662                     me.deferFocus();
109663                 }
109664             };
109665         }
109666
109667         return null; // not needed, so null
109668     }(),
109669
109670     /**
109671      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
109672      * @return {Ext.toolbar.Toolbar}
109673      */
109674     getToolbar : function(){
109675         return this.toolbar;
109676     },
109677
109678     /**
109679      * @property {Object} buttonTips
109680      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
109681      * that button and the value is a valid QuickTips object. For example:
109682      *
109683      *     {
109684      *         bold : {
109685      *             title: 'Bold (Ctrl+B)',
109686      *             text: 'Make the selected text bold.',
109687      *             cls: 'x-html-editor-tip'
109688      *         },
109689      *         italic : {
109690      *             title: 'Italic (Ctrl+I)',
109691      *             text: 'Make the selected text italic.',
109692      *             cls: 'x-html-editor-tip'
109693      *         },
109694      *         ...
109695      */
109696     buttonTips : {
109697         bold : {
109698             title: 'Bold (Ctrl+B)',
109699             text: 'Make the selected text bold.',
109700             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109701         },
109702         italic : {
109703             title: 'Italic (Ctrl+I)',
109704             text: 'Make the selected text italic.',
109705             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109706         },
109707         underline : {
109708             title: 'Underline (Ctrl+U)',
109709             text: 'Underline the selected text.',
109710             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109711         },
109712         increasefontsize : {
109713             title: 'Grow Text',
109714             text: 'Increase the font size.',
109715             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109716         },
109717         decreasefontsize : {
109718             title: 'Shrink Text',
109719             text: 'Decrease the font size.',
109720             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109721         },
109722         backcolor : {
109723             title: 'Text Highlight Color',
109724             text: 'Change the background color of the selected text.',
109725             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109726         },
109727         forecolor : {
109728             title: 'Font Color',
109729             text: 'Change the color of the selected text.',
109730             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109731         },
109732         justifyleft : {
109733             title: 'Align Text Left',
109734             text: 'Align text to the left.',
109735             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109736         },
109737         justifycenter : {
109738             title: 'Center Text',
109739             text: 'Center text in the editor.',
109740             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109741         },
109742         justifyright : {
109743             title: 'Align Text Right',
109744             text: 'Align text to the right.',
109745             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109746         },
109747         insertunorderedlist : {
109748             title: 'Bullet List',
109749             text: 'Start a bulleted list.',
109750             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109751         },
109752         insertorderedlist : {
109753             title: 'Numbered List',
109754             text: 'Start a numbered list.',
109755             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109756         },
109757         createlink : {
109758             title: 'Hyperlink',
109759             text: 'Make the selected text a hyperlink.',
109760             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109761         },
109762         sourceedit : {
109763             title: 'Source Edit',
109764             text: 'Switch to source editing mode.',
109765             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109766         }
109767     }
109768
109769     // hide stuff that is not compatible
109770     /**
109771      * @event blur
109772      * @hide
109773      */
109774     /**
109775      * @event change
109776      * @hide
109777      */
109778     /**
109779      * @event focus
109780      * @hide
109781      */
109782     /**
109783      * @event specialkey
109784      * @hide
109785      */
109786     /**
109787      * @cfg {String} fieldCls @hide
109788      */
109789     /**
109790      * @cfg {String} focusCls @hide
109791      */
109792     /**
109793      * @cfg {String} autoCreate @hide
109794      */
109795     /**
109796      * @cfg {String} inputType @hide
109797      */
109798     /**
109799      * @cfg {String} invalidCls @hide
109800      */
109801     /**
109802      * @cfg {String} invalidText @hide
109803      */
109804     /**
109805      * @cfg {String} msgFx @hide
109806      */
109807     /**
109808      * @cfg {Boolean} allowDomMove @hide
109809      */
109810     /**
109811      * @cfg {String} applyTo @hide
109812      */
109813     /**
109814      * @cfg {String} readOnly  @hide
109815      */
109816     /**
109817      * @cfg {String} tabIndex  @hide
109818      */
109819     /**
109820      * @method validate
109821      * @hide
109822      */
109823 });
109824
109825 /**
109826  * @docauthor Robert Dougan <rob@sencha.com>
109827  *
109828  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
109829  * at a time within a group of radios with the same name.
109830  *
109831  * # Labeling
109832  *
109833  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
109834  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
109835  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
109836  *
109837  * # Values
109838  *
109839  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
109840  *
109841  * The following values will check the radio:
109842  *
109843  * - `true`
109844  * - `'true'`
109845  * - `'1'`
109846  * - `'on'`
109847  *
109848  * Any other value will uncheck it.
109849  *
109850  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
109851  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
109852  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
109853  *
109854  * # Example usage
109855  *
109856  *     @example
109857  *     Ext.create('Ext.form.Panel', {
109858  *         title      : 'Order Form',
109859  *         width      : 300,
109860  *         bodyPadding: 10,
109861  *         renderTo   : Ext.getBody(),
109862  *         items: [
109863  *             {
109864  *                 xtype      : 'fieldcontainer',
109865  *                 fieldLabel : 'Size',
109866  *                 defaultType: 'radiofield',
109867  *                 defaults: {
109868  *                     flex: 1
109869  *                 },
109870  *                 layout: 'hbox',
109871  *                 items: [
109872  *                     {
109873  *                         boxLabel  : 'M',
109874  *                         name      : 'size',
109875  *                         inputValue: 'm',
109876  *                         id        : 'radio1'
109877  *                     }, {
109878  *                         boxLabel  : 'L',
109879  *                         name      : 'size',
109880  *                         inputValue: 'l',
109881  *                         id        : 'radio2'
109882  *                     }, {
109883  *                         boxLabel  : 'XL',
109884  *                         name      : 'size',
109885  *                         inputValue: 'xl',
109886  *                         id        : 'radio3'
109887  *                     }
109888  *                 ]
109889  *             },
109890  *             {
109891  *                 xtype      : 'fieldcontainer',
109892  *                 fieldLabel : 'Color',
109893  *                 defaultType: 'radiofield',
109894  *                 defaults: {
109895  *                     flex: 1
109896  *                 },
109897  *                 layout: 'hbox',
109898  *                 items: [
109899  *                     {
109900  *                         boxLabel  : 'Blue',
109901  *                         name      : 'color',
109902  *                         inputValue: 'blue',
109903  *                         id        : 'radio4'
109904  *                     }, {
109905  *                         boxLabel  : 'Grey',
109906  *                         name      : 'color',
109907  *                         inputValue: 'grey',
109908  *                         id        : 'radio5'
109909  *                     }, {
109910  *                         boxLabel  : 'Black',
109911  *                         name      : 'color',
109912  *                         inputValue: 'black',
109913  *                         id        : 'radio6'
109914  *                     }
109915  *                 ]
109916  *             }
109917  *         ],
109918  *         bbar: [
109919  *             {
109920  *                 text: 'Smaller Size',
109921  *                 handler: function() {
109922  *                     var radio1 = Ext.getCmp('radio1'),
109923  *                         radio2 = Ext.getCmp('radio2'),
109924  *                         radio3 = Ext.getCmp('radio3');
109925  *
109926  *                     //if L is selected, change to M
109927  *                     if (radio2.getValue()) {
109928  *                         radio1.setValue(true);
109929  *                         return;
109930  *                     }
109931  *
109932  *                     //if XL is selected, change to L
109933  *                     if (radio3.getValue()) {
109934  *                         radio2.setValue(true);
109935  *                         return;
109936  *                     }
109937  *
109938  *                     //if nothing is set, set size to S
109939  *                     radio1.setValue(true);
109940  *                 }
109941  *             },
109942  *             {
109943  *                 text: 'Larger Size',
109944  *                 handler: function() {
109945  *                     var radio1 = Ext.getCmp('radio1'),
109946  *                         radio2 = Ext.getCmp('radio2'),
109947  *                         radio3 = Ext.getCmp('radio3');
109948  *
109949  *                     //if M is selected, change to L
109950  *                     if (radio1.getValue()) {
109951  *                         radio2.setValue(true);
109952  *                         return;
109953  *                     }
109954  *
109955  *                     //if L is selected, change to XL
109956  *                     if (radio2.getValue()) {
109957  *                         radio3.setValue(true);
109958  *                         return;
109959  *                     }
109960  *
109961  *                     //if nothing is set, set size to XL
109962  *                     radio3.setValue(true);
109963  *                 }
109964  *             },
109965  *             '-',
109966  *             {
109967  *                 text: 'Select color',
109968  *                 menu: {
109969  *                     indent: false,
109970  *                     items: [
109971  *                         {
109972  *                             text: 'Blue',
109973  *                             handler: function() {
109974  *                                 var radio = Ext.getCmp('radio4');
109975  *                                 radio.setValue(true);
109976  *                             }
109977  *                         },
109978  *                         {
109979  *                             text: 'Grey',
109980  *                             handler: function() {
109981  *                                 var radio = Ext.getCmp('radio5');
109982  *                                 radio.setValue(true);
109983  *                             }
109984  *                         },
109985  *                         {
109986  *                             text: 'Black',
109987  *                             handler: function() {
109988  *                                 var radio = Ext.getCmp('radio6');
109989  *                                 radio.setValue(true);
109990  *                             }
109991  *                         }
109992  *                     ]
109993  *                 }
109994  *             }
109995  *         ]
109996  *     });
109997  */
109998 Ext.define('Ext.form.field.Radio', {
109999     extend:'Ext.form.field.Checkbox',
110000     alias: ['widget.radiofield', 'widget.radio'],
110001     alternateClassName: 'Ext.form.Radio',
110002     requires: ['Ext.form.RadioManager'],
110003
110004     isRadio: true,
110005
110006     /**
110007      * @cfg {String} uncheckedValue @hide
110008      */
110009
110010     // private
110011     inputType: 'radio',
110012     ariaRole: 'radio',
110013
110014     /**
110015      * If this radio is part of a group, it will return the selected value
110016      * @return {String}
110017      */
110018     getGroupValue: function() {
110019         var selected = this.getManager().getChecked(this.name);
110020         return selected ? selected.inputValue : null;
110021     },
110022
110023     /**
110024      * @private Handle click on the radio button
110025      */
110026     onBoxClick: function(e) {
110027         var me = this;
110028         if (!me.disabled && !me.readOnly) {
110029             this.setValue(true);
110030         }
110031     },
110032
110033     /**
110034      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
110035      * of the same name whose value is the value specified.
110036      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
110037      * @return {Ext.form.field.Radio} this
110038      */
110039     setValue: function(v) {
110040         var me = this,
110041             active;
110042
110043         if (Ext.isBoolean(v)) {
110044             me.callParent(arguments);
110045         } else {
110046             active = me.getManager().getWithValue(me.name, v).getAt(0);
110047             if (active) {
110048                 active.setValue(true);
110049             }
110050         }
110051         return me;
110052     },
110053
110054     /**
110055      * Returns the submit value for the checkbox which can be used when submitting forms.
110056      * @return {Boolean/Object} True if checked, null if not.
110057      */
110058     getSubmitValue: function() {
110059         return this.checked ? this.inputValue : null;
110060     },
110061
110062     getModelData: function() {
110063         return this.getSubmitData();
110064     },
110065
110066     // inherit docs
110067     onChange: function(newVal, oldVal) {
110068         var me = this;
110069         me.callParent(arguments);
110070
110071         if (newVal) {
110072             this.getManager().getByName(me.name).each(function(item){
110073                 if (item !== me) {
110074                     item.setValue(false);
110075                 }
110076             }, me);
110077         }
110078     },
110079
110080     // inherit docs
110081     getManager: function() {
110082         return Ext.form.RadioManager;
110083     }
110084 });
110085
110086 /**
110087  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
110088  * class to allow browsing and selection of valid times, but could also be used with other components.
110089  *
110090  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
110091  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
110092  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
110093  * config.
110094  *
110095  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
110096  *
110097  *     @example
110098  *     Ext.create('Ext.picker.Time', {
110099  *        width: 60,
110100  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
110101  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
110102  *        renderTo: Ext.getBody()
110103  *     });
110104  */
110105 Ext.define('Ext.picker.Time', {
110106     extend: 'Ext.view.BoundList',
110107     alias: 'widget.timepicker',
110108     requires: ['Ext.data.Store', 'Ext.Date'],
110109
110110     /**
110111      * @cfg {Date} minValue
110112      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
110113      * used); no parsing of String values will be done.
110114      */
110115
110116     /**
110117      * @cfg {Date} maxValue
110118      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
110119      * used); no parsing of String values will be done.
110120      */
110121
110122     /**
110123      * @cfg {Number} increment
110124      * The number of minutes between each time value in the list.
110125      */
110126     increment: 15,
110127
110128     /**
110129      * @cfg {String} format
110130      * The default time format string which can be overriden for localization support. The format must be valid
110131      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110132      * instead.
110133      */
110134     format : "g:i A",
110135
110136     /**
110137      * @hide
110138      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
110139      * an internal field name only and is not useful to change via config.
110140      */
110141     displayField: 'disp',
110142
110143     /**
110144      * @private
110145      * Year, month, and day that all times will be normalized into internally.
110146      */
110147     initDate: [2008,0,1],
110148
110149     componentCls: Ext.baseCSSPrefix + 'timepicker',
110150
110151     /**
110152      * @hide
110153      */
110154     loadMask: false,
110155
110156     initComponent: function() {
110157         var me = this,
110158             dateUtil = Ext.Date,
110159             clearTime = dateUtil.clearTime,
110160             initDate = me.initDate;
110161
110162         // Set up absolute min and max for the entire day
110163         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
110164         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
110165
110166         me.store = me.createStore();
110167         me.updateList();
110168
110169         me.callParent();
110170     },
110171
110172     /**
110173      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
110174      * fields will be used); no parsing of String values will be done.
110175      * @param {Date} value
110176      */
110177     setMinValue: function(value) {
110178         this.minValue = value;
110179         this.updateList();
110180     },
110181
110182     /**
110183      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
110184      * fields will be used); no parsing of String values will be done.
110185      * @param {Date} value
110186      */
110187     setMaxValue: function(value) {
110188         this.maxValue = value;
110189         this.updateList();
110190     },
110191
110192     /**
110193      * @private
110194      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
110195      * the time fields are significant. This makes values suitable for time comparison.
110196      * @param {Date} date
110197      */
110198     normalizeDate: function(date) {
110199         var initDate = this.initDate;
110200         date.setFullYear(initDate[0], initDate[1], initDate[2]);
110201         return date;
110202     },
110203
110204     /**
110205      * Update the list of available times in the list to be constrained within the {@link #minValue}
110206      * and {@link #maxValue}.
110207      */
110208     updateList: function() {
110209         var me = this,
110210             min = me.normalizeDate(me.minValue || me.absMin),
110211             max = me.normalizeDate(me.maxValue || me.absMax);
110212
110213         me.store.filterBy(function(record) {
110214             var date = record.get('date');
110215             return date >= min && date <= max;
110216         });
110217     },
110218
110219     /**
110220      * @private
110221      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
110222      * is loaded with all possible times, and it is later filtered to hide those times outside
110223      * the minValue/maxValue.
110224      */
110225     createStore: function() {
110226         var me = this,
110227             utilDate = Ext.Date,
110228             times = [],
110229             min = me.absMin,
110230             max = me.absMax;
110231
110232         while(min <= max){
110233             times.push({
110234                 disp: utilDate.dateFormat(min, me.format),
110235                 date: min
110236             });
110237             min = utilDate.add(min, 'mi', me.increment);
110238         }
110239
110240         return Ext.create('Ext.data.Store', {
110241             fields: ['disp', 'date'],
110242             data: times
110243         });
110244     }
110245
110246 });
110247
110248 /**
110249  * Provides a time input field with a time dropdown and automatic time validation.
110250  *
110251  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
110252  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
110253  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
110254  * the user's locale.
110255  *
110256  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
110257  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
110258  *
110259  * Example usage:
110260  *
110261  *     @example
110262  *     Ext.create('Ext.form.Panel', {
110263  *         title: 'Time Card',
110264  *         width: 300,
110265  *         bodyPadding: 10,
110266  *         renderTo: Ext.getBody(),
110267  *         items: [{
110268  *             xtype: 'timefield',
110269  *             name: 'in',
110270  *             fieldLabel: 'Time In',
110271  *             minValue: '6:00 AM',
110272  *             maxValue: '8:00 PM',
110273  *             increment: 30,
110274  *             anchor: '100%'
110275  *         }, {
110276  *             xtype: 'timefield',
110277  *             name: 'out',
110278  *             fieldLabel: 'Time Out',
110279  *             minValue: '6:00 AM',
110280  *             maxValue: '8:00 PM',
110281  *             increment: 30,
110282  *             anchor: '100%'
110283  *        }]
110284  *     });
110285  */
110286 Ext.define('Ext.form.field.Time', {
110287     extend:'Ext.form.field.Picker',
110288     alias: 'widget.timefield',
110289     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
110290     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
110291
110292     /**
110293      * @cfg {String} triggerCls
110294      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
110295      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
110296      * trigger.
110297      */
110298     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
110299
110300     /**
110301      * @cfg {Date/String} minValue
110302      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110303      * valid format -- see {@link #format} and {@link #altFormats}.
110304      */
110305
110306     /**
110307      * @cfg {Date/String} maxValue
110308      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110309      * valid format -- see {@link #format} and {@link #altFormats}.
110310      */
110311
110312     /**
110313      * @cfg {String} minText
110314      * The error text to display when the entered time is before {@link #minValue}.
110315      */
110316     minText : "The time in this field must be equal to or after {0}",
110317
110318     /**
110319      * @cfg {String} maxText
110320      * The error text to display when the entered time is after {@link #maxValue}.
110321      */
110322     maxText : "The time in this field must be equal to or before {0}",
110323
110324     /**
110325      * @cfg {String} invalidText
110326      * The error text to display when the time in the field is invalid.
110327      */
110328     invalidText : "{0} is not a valid time",
110329
110330     /**
110331      * @cfg {String} format
110332      * The default time format string which can be overriden for localization support. The format must be valid
110333      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110334      * instead.
110335      */
110336     format : "g:i A",
110337
110338     /**
110339      * @cfg {String} submitFormat
110340      * The date format string which will be submitted to the server. The format must be valid according to {@link
110341      * Ext.Date#parse} (defaults to {@link #format}).
110342      */
110343
110344     /**
110345      * @cfg {String} altFormats
110346      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
110347      * format.
110348      */
110349     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",
110350
110351     /**
110352      * @cfg {Number} increment
110353      * The number of minutes between each time value in the list.
110354      */
110355     increment: 15,
110356
110357     /**
110358      * @cfg {Number} pickerMaxHeight
110359      * The maximum height of the {@link Ext.picker.Time} dropdown.
110360      */
110361     pickerMaxHeight: 300,
110362
110363     /**
110364      * @cfg {Boolean} selectOnTab
110365      * Whether the Tab key should select the currently highlighted item.
110366      */
110367     selectOnTab: true,
110368
110369     /**
110370      * @private
110371      * This is the date to use when generating time values in the absence of either minValue
110372      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
110373      * arbitrary "safe" date that can be any date aside from DST boundary dates.
110374      */
110375     initDate: '1/1/2008',
110376     initDateFormat: 'j/n/Y',
110377
110378
110379     initComponent: function() {
110380         var me = this,
110381             min = me.minValue,
110382             max = me.maxValue;
110383         if (min) {
110384             me.setMinValue(min);
110385         }
110386         if (max) {
110387             me.setMaxValue(max);
110388         }
110389         this.callParent();
110390     },
110391
110392     initValue: function() {
110393         var me = this,
110394             value = me.value;
110395
110396         // If a String value was supplied, try to convert it to a proper Date object
110397         if (Ext.isString(value)) {
110398             me.value = me.rawToValue(value);
110399         }
110400
110401         me.callParent();
110402     },
110403
110404     /**
110405      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
110406      * @param {Date/String} value The minimum time that can be selected
110407      */
110408     setMinValue: function(value) {
110409         var me = this,
110410             picker = me.picker;
110411         me.setLimit(value, true);
110412         if (picker) {
110413             picker.setMinValue(me.minValue);
110414         }
110415     },
110416
110417     /**
110418      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
110419      * @param {Date/String} value The maximum time that can be selected
110420      */
110421     setMaxValue: function(value) {
110422         var me = this,
110423             picker = me.picker;
110424         me.setLimit(value, false);
110425         if (picker) {
110426             picker.setMaxValue(me.maxValue);
110427         }
110428     },
110429
110430     /**
110431      * @private
110432      * Updates either the min or max value. Converts the user's value into a Date object whose
110433      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
110434      */
110435     setLimit: function(value, isMin) {
110436         var me = this,
110437             d, val;
110438         if (Ext.isString(value)) {
110439             d = me.parseDate(value);
110440         }
110441         else if (Ext.isDate(value)) {
110442             d = value;
110443         }
110444         if (d) {
110445             val = Ext.Date.clearTime(new Date(me.initDate));
110446             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
110447             me[isMin ? 'minValue' : 'maxValue'] = val;
110448         }
110449     },
110450
110451     rawToValue: function(rawValue) {
110452         return this.parseDate(rawValue) || rawValue || null;
110453     },
110454
110455     valueToRaw: function(value) {
110456         return this.formatDate(this.parseDate(value));
110457     },
110458
110459     /**
110460      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
110461      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
110462      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
110463      * set.
110464      * @param {Object} [value] The value to get errors for (defaults to the current field value)
110465      * @return {String[]} All validation errors for this field
110466      */
110467     getErrors: function(value) {
110468         var me = this,
110469             format = Ext.String.format,
110470             errors = me.callParent(arguments),
110471             minValue = me.minValue,
110472             maxValue = me.maxValue,
110473             date;
110474
110475         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
110476
110477         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
110478              return errors;
110479         }
110480
110481         date = me.parseDate(value);
110482         if (!date) {
110483             errors.push(format(me.invalidText, value, me.format));
110484             return errors;
110485         }
110486
110487         if (minValue && date < minValue) {
110488             errors.push(format(me.minText, me.formatDate(minValue)));
110489         }
110490
110491         if (maxValue && date > maxValue) {
110492             errors.push(format(me.maxText, me.formatDate(maxValue)));
110493         }
110494
110495         return errors;
110496     },
110497
110498     formatDate: function() {
110499         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
110500     },
110501
110502     /**
110503      * @private
110504      * Parses an input value into a valid Date object.
110505      * @param {String/Date} value
110506      */
110507     parseDate: function(value) {
110508         if (!value || Ext.isDate(value)) {
110509             return value;
110510         }
110511
110512         var me = this,
110513             val = me.safeParse(value, me.format),
110514             altFormats = me.altFormats,
110515             altFormatsArray = me.altFormatsArray,
110516             i = 0,
110517             len;
110518
110519         if (!val && altFormats) {
110520             altFormatsArray = altFormatsArray || altFormats.split('|');
110521             len = altFormatsArray.length;
110522             for (; i < len && !val; ++i) {
110523                 val = me.safeParse(value, altFormatsArray[i]);
110524             }
110525         }
110526         return val;
110527     },
110528
110529     safeParse: function(value, format){
110530         var me = this,
110531             utilDate = Ext.Date,
110532             parsedDate,
110533             result = null;
110534
110535         if (utilDate.formatContainsDateInfo(format)) {
110536             // assume we've been given a full date
110537             result = utilDate.parse(value, format);
110538         } else {
110539             // Use our initial safe date
110540             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
110541             if (parsedDate) {
110542                 result = parsedDate;
110543             }
110544         }
110545         return result;
110546     },
110547
110548     // @private
110549     getSubmitValue: function() {
110550         var me = this,
110551             format = me.submitFormat || me.format,
110552             value = me.getValue();
110553
110554         return value ? Ext.Date.format(value, format) : null;
110555     },
110556
110557     /**
110558      * @private
110559      * Creates the {@link Ext.picker.Time}
110560      */
110561     createPicker: function() {
110562         var me = this,
110563             picker = Ext.create('Ext.picker.Time', {
110564                 pickerField: me,
110565                 selModel: {
110566                     mode: 'SINGLE'
110567                 },
110568                 floating: true,
110569                 hidden: true,
110570                 minValue: me.minValue,
110571                 maxValue: me.maxValue,
110572                 increment: me.increment,
110573                 format: me.format,
110574                 ownerCt: this.ownerCt,
110575                 renderTo: document.body,
110576                 maxHeight: me.pickerMaxHeight,
110577                 focusOnToFront: false
110578             });
110579
110580         me.mon(picker.getSelectionModel(), {
110581             selectionchange: me.onListSelect,
110582             scope: me
110583         });
110584
110585         return picker;
110586     },
110587
110588     /**
110589      * @private
110590      * Enables the key nav for the Time picker when it is expanded.
110591      * TODO this is largely the same logic as ComboBox, should factor out.
110592      */
110593     onExpand: function() {
110594         var me = this,
110595             keyNav = me.pickerKeyNav,
110596             selectOnTab = me.selectOnTab,
110597             picker = me.getPicker(),
110598             lastSelected = picker.getSelectionModel().lastSelected,
110599             itemNode;
110600
110601         if (!keyNav) {
110602             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
110603                 boundList: picker,
110604                 forceKeyDown: true,
110605                 tab: function(e) {
110606                     if (selectOnTab) {
110607                         if(me.picker.highlightedItem) {
110608                             this.selectHighlighted(e);
110609                         } else {
110610                             me.collapse();
110611                         }
110612                         me.triggerBlur();
110613                     }
110614                     // Tab key event is allowed to propagate to field
110615                     return true;
110616                 }
110617             });
110618             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
110619             if (selectOnTab) {
110620                 me.ignoreMonitorTab = true;
110621             }
110622         }
110623         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
110624
110625         // Highlight the last selected item and scroll it into view
110626         if (lastSelected) {
110627             itemNode = picker.getNode(lastSelected);
110628             if (itemNode) {
110629                 picker.highlightItem(itemNode);
110630                 picker.el.scrollChildIntoView(itemNode, false);
110631             }
110632         }
110633     },
110634
110635     /**
110636      * @private
110637      * Disables the key nav for the Time picker when it is collapsed.
110638      */
110639     onCollapse: function() {
110640         var me = this,
110641             keyNav = me.pickerKeyNav;
110642         if (keyNav) {
110643             keyNav.disable();
110644             me.ignoreMonitorTab = false;
110645         }
110646     },
110647
110648     /**
110649      * @private
110650      * Clears the highlighted item in the picker on change.
110651      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
110652      */
110653     onChange: function() {
110654         var me = this,
110655             picker = me.picker;
110656
110657         me.callParent(arguments);
110658         if(picker) {
110659             picker.clearHighlight();
110660         }
110661     },
110662
110663     /**
110664      * @private
110665      * Handles a time being selected from the Time picker.
110666      */
110667     onListSelect: function(list, recordArray) {
110668         var me = this,
110669             record = recordArray[0],
110670             val = record ? record.get('date') : null;
110671         me.setValue(val);
110672         me.fireEvent('select', me, val);
110673         me.picker.clearHighlight();
110674         me.collapse();
110675         me.inputEl.focus();
110676     }
110677 });
110678
110679
110680 /**
110681  * @class Ext.grid.CellEditor
110682  * @extends Ext.Editor
110683  * Internal utility class that provides default configuration for cell editing.
110684  * @ignore
110685  */
110686 Ext.define('Ext.grid.CellEditor', {
110687     extend: 'Ext.Editor',
110688     constructor: function(config) {
110689         config = Ext.apply({}, config);
110690         
110691         if (config.field) {
110692             config.field.monitorTab = false;
110693         }
110694         if (!Ext.isDefined(config.autoSize)) {
110695             config.autoSize = {
110696                 width: 'boundEl'
110697             };
110698         }
110699         this.callParent([config]);
110700     },
110701     
110702     /**
110703      * @private
110704      * Hide the grid cell when editor is shown.
110705      */
110706     onShow: function() {
110707         var first = this.boundEl.first();
110708         if (first) {
110709             first.hide();
110710         }
110711         this.callParent(arguments);
110712     },
110713     
110714     /**
110715      * @private
110716      * Show grid cell when editor is hidden.
110717      */
110718     onHide: function() {
110719         var first = this.boundEl.first();
110720         if (first) {
110721             first.show();
110722         }
110723         this.callParent(arguments);
110724     },
110725     
110726     /**
110727      * @private
110728      * Fix checkbox blur when it is clicked.
110729      */
110730     afterRender: function() {
110731         this.callParent(arguments);
110732         var field = this.field;
110733         if (field.isXType('checkboxfield')) {
110734             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
110735             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
110736         }
110737     },
110738     
110739     /**
110740      * @private
110741      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
110742      */
110743     onCheckBoxMouseDown: function() {
110744         this.completeEdit = Ext.emptyFn;
110745     },
110746     
110747     /**
110748      * @private
110749      * Restore checkbox focus and completeEdit method.
110750      */
110751     onCheckBoxClick: function() {
110752         delete this.completeEdit;
110753         this.field.focus(false, 10);
110754     },
110755     
110756     alignment: "tl-tl",
110757     hideEl : false,
110758     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
110759     shim: false,
110760     shadow: false
110761 });
110762 /**
110763  * @class Ext.grid.ColumnLayout
110764  * @extends Ext.layout.container.HBox
110765  * @private
110766  *
110767  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
110768  *
110769  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
110770  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
110771  *
110772  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
110773  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
110774  */
110775 Ext.define('Ext.grid.ColumnLayout', {
110776     extend: 'Ext.layout.container.HBox',
110777     alias: 'layout.gridcolumn',
110778     type : 'column',
110779
110780     reserveOffset: false,
110781
110782     shrinkToFit: false,
110783
110784     // Height-stretched innerCt must be able to revert back to unstretched height
110785     clearInnerCtOnLayout: true,
110786
110787     beforeLayout: function() {
110788         var me = this,
110789             i = 0,
110790             items = me.getLayoutItems(),
110791             len = items.length,
110792             item, returnValue,
110793             s;
110794
110795         // Scrollbar offset defined by width of any vertical scroller in the owning grid
110796         if (!Ext.isDefined(me.availableSpaceOffset)) {
110797             s = me.owner.up('tablepanel').verticalScroller;
110798             me.availableSpaceOffset = s ? s.width-1 : 0;
110799         }
110800
110801         returnValue = me.callParent(arguments);
110802
110803         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
110804         me.innerCt.setHeight(23);
110805
110806         // Unstretch child items before the layout which stretches them.
110807         for (; i < len; i++) {
110808             item = items[i];
110809             item.el.setStyle({
110810                 height: 'auto'
110811             });
110812             item.titleContainer.setStyle({
110813                 height: 'auto',
110814                 paddingTop: '0'
110815             });
110816             if (item.componentLayout && item.componentLayout.lastComponentSize) {
110817                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
110818             }
110819         }
110820         return returnValue;
110821     },
110822
110823     // Override to enforce the forceFit config.
110824     calculateChildBoxes: function(visibleItems, targetSize) {
110825         var me = this,
110826             calculations = me.callParent(arguments),
110827             boxes = calculations.boxes,
110828             metaData = calculations.meta,
110829             len = boxes.length, i = 0, box, item;
110830
110831         if (targetSize.width && !me.isHeader) {
110832             // If configured forceFit then all columns will be flexed
110833             if (me.owner.forceFit) {
110834
110835                 for (; i < len; i++) {
110836                     box = boxes[i];
110837                     item = box.component;
110838
110839                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
110840                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
110841
110842                     // For forceFit, just use allocated width as the flex value, and the proportions
110843                     // will end up the same whatever HeaderContainer width they are being forced into.
110844                     item.flex = box.width;
110845                 }
110846
110847                 // Recalculate based upon all columns now being flexed instead of sized.
110848                 calculations = me.callParent(arguments);
110849             }
110850             else if (metaData.tooNarrow) {
110851                 targetSize.width = metaData.desiredSize;
110852             }
110853         }
110854
110855         return calculations;
110856     },
110857
110858     afterLayout: function() {
110859         var me = this,
110860             owner = me.owner,
110861             topGrid,
110862             bothHeaderCts,
110863             otherHeaderCt,
110864             thisHeight,
110865             otherHeight,
110866             modifiedGrid,
110867             i = 0,
110868             items,
110869             len,
110870             headerHeight;
110871
110872         me.callParent(arguments);
110873
110874         // Set up padding in items
110875         if (!me.owner.hideHeaders) {
110876
110877             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
110878             // of the highest one, and sync the other one to that height.
110879             if (owner.lockableInjected) {
110880                 topGrid = owner.up('tablepanel').up('tablepanel');
110881                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
110882                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
110883
110884                 // Both sides must be rendered for this syncing operation to work.
110885                 if (!otherHeaderCt.rendered) {
110886                     return;
110887                 }
110888
110889                 // Get the height of the highest of both HeaderContainers
110890                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
110891                 if (!otherHeight) {
110892                     return;
110893                 }
110894                 thisHeight = this.getRenderTarget().getViewSize().height;
110895                 if (!thisHeight) {
110896                     return;
110897                 }
110898
110899                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
110900                 // Block the upward notification by flagging the top grid's component layout as busy.
110901                 topGrid.componentLayout.layoutBusy = true;
110902
110903                 // Assume that the correct header height is the height of this HeaderContainer
110904                 headerHeight = thisHeight;
110905
110906                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
110907                 if (thisHeight > otherHeight) {
110908                     otherHeaderCt.layout.align = 'stretch';
110909                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
110910                     delete otherHeaderCt.layout.align;
110911                     modifiedGrid = otherHeaderCt.up('tablepanel');
110912                 } else if (otherHeight > thisHeight) {
110913                     headerHeight = otherHeight;
110914                     this.align = 'stretch';
110915                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
110916                     delete this.align;
110917                     modifiedGrid = owner.up('tablepanel');
110918                 }
110919                 topGrid.componentLayout.layoutBusy = false;
110920
110921                 // Gather all Header items across both Grids.
110922                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
110923             } else {
110924                 headerHeight = this.getRenderTarget().getViewSize().height;
110925                 items = me.getLayoutItems();
110926             }
110927
110928             len = items.length;
110929             for (; i < len; i++) {
110930                 items[i].setPadding(headerHeight);
110931             }
110932
110933             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
110934             if (modifiedGrid) {
110935                 setTimeout(function() {
110936                     modifiedGrid.doLayout();
110937                 }, 1);
110938             }
110939         }
110940     },
110941
110942     // FIX: when flexing we actually don't have enough space as we would
110943     // typically because of the scrollOffset on the GridView, must reserve this
110944     updateInnerCtSize: function(tSize, calcs) {
110945         var me = this,
110946             extra;
110947
110948         // Columns must not account for scroll offset
110949         if (!me.isHeader) {
110950             me.tooNarrow = calcs.meta.tooNarrow;
110951             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
110952
110953             if (calcs.meta.tooNarrow) {
110954                 tSize.width = calcs.meta.desiredSize + extra;
110955             } else {
110956                 tSize.width += extra;
110957             }
110958         }
110959
110960         return me.callParent(arguments);
110961     },
110962
110963     doOwnerCtLayouts: function() {
110964         var ownerCt = this.owner.ownerCt;
110965         if (!ownerCt.componentLayout.layoutBusy) {
110966             ownerCt.doComponentLayout();
110967         }
110968     }
110969 });
110970 /**
110971  * @class Ext.grid.LockingView
110972  * This class is used internally to provide a single interface when using
110973  * a locking grid. Internally, the locking grid creates two separate grids,
110974  * so this class is used to map calls appropriately.
110975  * @ignore
110976  */
110977 Ext.define('Ext.grid.LockingView', {
110978
110979     mixins: {
110980         observable: 'Ext.util.Observable'
110981     },
110982
110983     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
110984
110985     constructor: function(config){
110986         var me = this,
110987             eventNames = [],
110988             eventRe = me.eventRelayRe,
110989             locked = config.locked.getView(),
110990             normal = config.normal.getView(),
110991             events,
110992             event;
110993
110994         Ext.apply(me, {
110995             lockedView: locked,
110996             normalView: normal,
110997             lockedGrid: config.locked,
110998             normalGrid: config.normal,
110999             panel: config.panel
111000         });
111001         me.mixins.observable.constructor.call(me, config);
111002
111003         // relay events
111004         events = locked.events;
111005         for (event in events) {
111006             if (events.hasOwnProperty(event) && eventRe.test(event)) {
111007                 eventNames.push(event);
111008             }
111009         }
111010         me.relayEvents(locked, eventNames);
111011         me.relayEvents(normal, eventNames);
111012
111013         normal.on({
111014             scope: me,
111015             itemmouseleave: me.onItemMouseLeave,
111016             itemmouseenter: me.onItemMouseEnter
111017         });
111018
111019         locked.on({
111020             scope: me,
111021             itemmouseleave: me.onItemMouseLeave,
111022             itemmouseenter: me.onItemMouseEnter
111023         });
111024     },
111025
111026     getGridColumns: function() {
111027         var cols = this.lockedGrid.headerCt.getGridColumns();
111028         return cols.concat(this.normalGrid.headerCt.getGridColumns());
111029     },
111030
111031     getEl: function(column){
111032         return this.getViewForColumn(column).getEl();
111033     },
111034
111035     getViewForColumn: function(column) {
111036         var view = this.lockedView,
111037             inLocked;
111038
111039         view.headerCt.cascade(function(col){
111040             if (col === column) {
111041                 inLocked = true;
111042                 return false;
111043             }
111044         });
111045
111046         return inLocked ? view : this.normalView;
111047     },
111048
111049     onItemMouseEnter: function(view, record){
111050         var me = this,
111051             locked = me.lockedView,
111052             other = me.normalView,
111053             item;
111054
111055         if (view.trackOver) {
111056             if (view !== locked) {
111057                 other = locked;
111058             }
111059             item = other.getNode(record);
111060             other.highlightItem(item);
111061         }
111062     },
111063
111064     onItemMouseLeave: function(view, record){
111065         var me = this,
111066             locked = me.lockedView,
111067             other = me.normalView;
111068
111069         if (view.trackOver) {
111070             if (view !== locked) {
111071                 other = locked;
111072             }
111073             other.clearHighlight();
111074         }
111075     },
111076
111077     relayFn: function(name, args){
111078         args = args || [];
111079
111080         var view = this.lockedView;
111081         view[name].apply(view, args || []);
111082         view = this.normalView;
111083         view[name].apply(view, args || []);
111084     },
111085
111086     getSelectionModel: function(){
111087         return this.panel.getSelectionModel();
111088     },
111089
111090     getStore: function(){
111091         return this.panel.store;
111092     },
111093
111094     getNode: function(nodeInfo){
111095         // default to the normal view
111096         return this.normalView.getNode(nodeInfo);
111097     },
111098
111099     getCell: function(record, column){
111100         var view = this.getViewForColumn(column),
111101             row;
111102
111103         row = view.getNode(record);
111104         return Ext.fly(row).down(column.getCellSelector());
111105     },
111106
111107     getRecord: function(node){
111108         var result = this.lockedView.getRecord(node);
111109         if (!node) {
111110             result = this.normalView.getRecord(node);
111111         }
111112         return result;
111113     },
111114
111115     addElListener: function(eventName, fn, scope){
111116         this.relayFn('addElListener', arguments);
111117     },
111118
111119     refreshNode: function(){
111120         this.relayFn('refreshNode', arguments);
111121     },
111122
111123     refresh: function(){
111124         this.relayFn('refresh', arguments);
111125     },
111126
111127     bindStore: function(){
111128         this.relayFn('bindStore', arguments);
111129     },
111130
111131     addRowCls: function(){
111132         this.relayFn('addRowCls', arguments);
111133     },
111134
111135     removeRowCls: function(){
111136         this.relayFn('removeRowCls', arguments);
111137     }
111138
111139 });
111140 /**
111141  * @class Ext.grid.Lockable
111142  * @private
111143  *
111144  * Lockable is a private mixin which injects lockable behavior into any
111145  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
111146  * automatically inject the Ext.grid.Lockable mixin in when one of the
111147  * these conditions are met:
111148  *
111149  *  - The TablePanel has the lockable configuration set to true
111150  *  - One of the columns in the TablePanel has locked set to true/false
111151  *
111152  * Each TablePanel subclass must register an alias. It should have an array
111153  * of configurations to copy to the 2 separate tablepanel's that will be generated
111154  * to note what configurations should be copied. These are named normalCfgCopy and
111155  * lockedCfgCopy respectively.
111156  *
111157  * Columns which are locked must specify a fixed width. They do NOT support a
111158  * flex width.
111159  *
111160  * Configurations which are specified in this class will be available on any grid or
111161  * tree which is using the lockable functionality.
111162  */
111163 Ext.define('Ext.grid.Lockable', {
111164
111165     requires: ['Ext.grid.LockingView'],
111166
111167     /**
111168      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
111169      * locked grid view. This is turned on by default. If your grid is guaranteed
111170      * to have rows of all the same height, you should set this to false to
111171      * optimize performance.
111172      */
111173     syncRowHeight: true,
111174
111175     /**
111176      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
111177      * not specified lockable will determine the subgrid xtype to create by the
111178      * following rule. Use the superclasses xtype if the superclass is NOT
111179      * tablepanel, otherwise use the xtype itself.
111180      */
111181
111182     /**
111183      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
111184      * locked side of the grid. Any conflicting configurations between lockedViewConfig
111185      * and viewConfig will be overwritten by the lockedViewConfig.
111186      */
111187
111188     /**
111189      * @cfg {Object} normalViewConfig A view configuration to be applied to the
111190      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
111191      * and viewConfig will be overwritten by the normalViewConfig.
111192      */
111193
111194     // private variable to track whether or not the spacer is hidden/visible
111195     spacerHidden: true,
111196
111197     headerCounter: 0,
111198
111199     // i8n text
111200     unlockText: 'Unlock',
111201     lockText: 'Lock',
111202
111203     determineXTypeToCreate: function() {
111204         var me = this,
111205             typeToCreate;
111206
111207         if (me.subGridXType) {
111208             typeToCreate = me.subGridXType;
111209         } else {
111210             var xtypes     = this.getXTypes().split('/'),
111211                 xtypesLn   = xtypes.length,
111212                 xtype      = xtypes[xtypesLn - 1],
111213                 superxtype = xtypes[xtypesLn - 2];
111214
111215             if (superxtype !== 'tablepanel') {
111216                 typeToCreate = superxtype;
111217             } else {
111218                 typeToCreate = xtype;
111219             }
111220         }
111221
111222         return typeToCreate;
111223     },
111224
111225     // injectLockable will be invoked before initComponent's parent class implementation
111226     // is called, so throughout this method this. are configurations
111227     injectLockable: function() {
111228         // ensure lockable is set to true in the TablePanel
111229         this.lockable = true;
111230         // Instruct the TablePanel it already has a view and not to create one.
111231         // We are going to aggregate 2 copies of whatever TablePanel we are using
111232         this.hasView = true;
111233
111234         var me = this,
111235             // xtype of this class, 'treepanel' or 'gridpanel'
111236             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
111237             // alias.)
111238             xtype = me.determineXTypeToCreate(),
111239             // share the selection model
111240             selModel = me.getSelectionModel(),
111241             lockedGrid = {
111242                 xtype: xtype,
111243                 // Lockable does NOT support animations for Tree
111244                 enableAnimations: false,
111245                 scroll: false,
111246                 scrollerOwner: false,
111247                 selModel: selModel,
111248                 border: false,
111249                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
111250             },
111251             normalGrid = {
111252                 xtype: xtype,
111253                 enableAnimations: false,
111254                 scrollerOwner: false,
111255                 selModel: selModel,
111256                 border: false
111257             },
111258             i = 0,
111259             columns,
111260             lockedHeaderCt,
111261             normalHeaderCt;
111262
111263         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
111264
111265         // copy appropriate configurations to the respective
111266         // aggregated tablepanel instances and then delete them
111267         // from the master tablepanel.
111268         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
111269         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
111270         for (; i < me.normalCfgCopy.length; i++) {
111271             delete me[me.normalCfgCopy[i]];
111272         }
111273         for (i = 0; i < me.lockedCfgCopy.length; i++) {
111274             delete me[me.lockedCfgCopy[i]];
111275         }
111276
111277         me.addEvents(
111278             /**
111279              * @event lockcolumn
111280              * Fires when a column is locked.
111281              * @param {Ext.grid.Panel} this The gridpanel.
111282              * @param {Ext.grid.column.Column} column The column being locked.
111283              */
111284             'lockcolumn',
111285
111286             /**
111287              * @event unlockcolumn
111288              * Fires when a column is unlocked.
111289              * @param {Ext.grid.Panel} this The gridpanel.
111290              * @param {Ext.grid.column.Column} column The column being unlocked.
111291              */
111292             'unlockcolumn'
111293         );
111294
111295         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
111296
111297         me.lockedHeights = [];
111298         me.normalHeights = [];
111299
111300         columns = me.processColumns(me.columns);
111301
111302         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
111303         lockedGrid.columns = columns.locked;
111304         normalGrid.columns = columns.normal;
111305
111306         me.store = Ext.StoreManager.lookup(me.store);
111307         lockedGrid.store = me.store;
111308         normalGrid.store = me.store;
111309
111310         // normal grid should flex the rest of the width
111311         normalGrid.flex = 1;
111312         lockedGrid.viewConfig = me.lockedViewConfig || {};
111313         lockedGrid.viewConfig.loadingUseMsg = false;
111314         normalGrid.viewConfig = me.normalViewConfig || {};
111315
111316         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
111317         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
111318
111319         me.normalGrid = Ext.ComponentManager.create(normalGrid);
111320         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
111321
111322         me.view = Ext.create('Ext.grid.LockingView', {
111323             locked: me.lockedGrid,
111324             normal: me.normalGrid,
111325             panel: me
111326         });
111327
111328         if (me.syncRowHeight) {
111329             me.lockedGrid.getView().on({
111330                 refresh: me.onLockedGridAfterRefresh,
111331                 itemupdate: me.onLockedGridAfterUpdate,
111332                 scope: me
111333             });
111334
111335             me.normalGrid.getView().on({
111336                 refresh: me.onNormalGridAfterRefresh,
111337                 itemupdate: me.onNormalGridAfterUpdate,
111338                 scope: me
111339             });
111340         }
111341
111342         lockedHeaderCt = me.lockedGrid.headerCt;
111343         normalHeaderCt = me.normalGrid.headerCt;
111344
111345         lockedHeaderCt.lockedCt = true;
111346         lockedHeaderCt.lockableInjected = true;
111347         normalHeaderCt.lockableInjected = true;
111348
111349         lockedHeaderCt.on({
111350             columnshow: me.onLockedHeaderShow,
111351             columnhide: me.onLockedHeaderHide,
111352             columnmove: me.onLockedHeaderMove,
111353             sortchange: me.onLockedHeaderSortChange,
111354             columnresize: me.onLockedHeaderResize,
111355             scope: me
111356         });
111357
111358         normalHeaderCt.on({
111359             columnmove: me.onNormalHeaderMove,
111360             sortchange: me.onNormalHeaderSortChange,
111361             scope: me
111362         });
111363
111364         me.normalGrid.on({
111365             scrollershow: me.onScrollerShow,
111366             scrollerhide: me.onScrollerHide,
111367             scope: me
111368         });
111369
111370         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
111371
111372         me.modifyHeaderCt();
111373         me.items = [me.lockedGrid, me.normalGrid];
111374
111375         me.relayHeaderCtEvents(lockedHeaderCt);
111376         me.relayHeaderCtEvents(normalHeaderCt);
111377
111378         me.layout = {
111379             type: 'hbox',
111380             align: 'stretch'
111381         };
111382     },
111383
111384     processColumns: function(columns){
111385         // split apart normal and lockedWidths
111386         var i = 0,
111387             len = columns.length,
111388             lockedWidth = 1,
111389             lockedHeaders = [],
111390             normalHeaders = [],
111391             column;
111392
111393         for (; i < len; ++i) {
111394             column = columns[i];
111395             // mark the column as processed so that the locked attribute does not
111396             // trigger trying to aggregate the columns again.
111397             column.processed = true;
111398             if (column.locked) {
111399                 if (!column.hidden) {
111400                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
111401                 }
111402                 lockedHeaders.push(column);
111403             } else {
111404                 normalHeaders.push(column);
111405             }
111406             if (!column.headerId) {
111407                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
111408             }
111409         }
111410         return {
111411             lockedWidth: lockedWidth,
111412             locked: lockedHeaders,
111413             normal: normalHeaders
111414         };
111415     },
111416
111417     // create a new spacer after the table is refreshed
111418     onLockedGridAfterLayout: function() {
111419         var me         = this,
111420             lockedView = me.lockedGrid.getView();
111421         lockedView.on({
111422             beforerefresh: me.destroySpacer,
111423             scope: me
111424         });
111425     },
111426
111427     // trigger a pseudo refresh on the normal side
111428     onLockedHeaderMove: function() {
111429         if (this.syncRowHeight) {
111430             this.onNormalGridAfterRefresh();
111431         }
111432     },
111433
111434     // trigger a pseudo refresh on the locked side
111435     onNormalHeaderMove: function() {
111436         if (this.syncRowHeight) {
111437             this.onLockedGridAfterRefresh();
111438         }
111439     },
111440
111441     // create a spacer in lockedsection and store a reference
111442     // TODO: Should destroy before refreshing content
111443     getSpacerEl: function() {
111444         var me   = this,
111445             w,
111446             view,
111447             el;
111448
111449         if (!me.spacerEl) {
111450             // This affects scrolling all the way to the bottom of a locked grid
111451             // additional test, sort a column and make sure it synchronizes
111452             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
111453             view = me.lockedGrid.getView();
111454             el   = view.el;
111455
111456             me.spacerEl = Ext.DomHelper.append(el, {
111457                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
111458                 style: 'height: ' + w + 'px;'
111459             }, true);
111460         }
111461         return me.spacerEl;
111462     },
111463
111464     destroySpacer: function() {
111465         var me = this;
111466         if (me.spacerEl) {
111467             me.spacerEl.destroy();
111468             delete me.spacerEl;
111469         }
111470     },
111471
111472     // cache the heights of all locked rows and sync rowheights
111473     onLockedGridAfterRefresh: function() {
111474         var me     = this,
111475             view   = me.lockedGrid.getView(),
111476             el     = view.el,
111477             rowEls = el.query(view.getItemSelector()),
111478             ln     = rowEls.length,
111479             i = 0;
111480
111481         // reset heights each time.
111482         me.lockedHeights = [];
111483
111484         for (; i < ln; i++) {
111485             me.lockedHeights[i] = rowEls[i].clientHeight;
111486         }
111487         me.syncRowHeights();
111488     },
111489
111490     // cache the heights of all normal rows and sync rowheights
111491     onNormalGridAfterRefresh: function() {
111492         var me     = this,
111493             view   = me.normalGrid.getView(),
111494             el     = view.el,
111495             rowEls = el.query(view.getItemSelector()),
111496             ln     = rowEls.length,
111497             i = 0;
111498
111499         // reset heights each time.
111500         me.normalHeights = [];
111501
111502         for (; i < ln; i++) {
111503             me.normalHeights[i] = rowEls[i].clientHeight;
111504         }
111505         me.syncRowHeights();
111506     },
111507
111508     // rows can get bigger/smaller
111509     onLockedGridAfterUpdate: function(record, index, node) {
111510         this.lockedHeights[index] = node.clientHeight;
111511         this.syncRowHeights();
111512     },
111513
111514     // rows can get bigger/smaller
111515     onNormalGridAfterUpdate: function(record, index, node) {
111516         this.normalHeights[index] = node.clientHeight;
111517         this.syncRowHeights();
111518     },
111519
111520     // match the rowheights to the biggest rowheight on either
111521     // side
111522     syncRowHeights: function() {
111523         var me = this,
111524             lockedHeights = me.lockedHeights,
111525             normalHeights = me.normalHeights,
111526             calcHeights   = [],
111527             ln = lockedHeights.length,
111528             i  = 0,
111529             lockedView, normalView,
111530             lockedRowEls, normalRowEls,
111531             vertScroller = me.getVerticalScroller(),
111532             scrollTop;
111533
111534         // ensure there are an equal num of locked and normal
111535         // rows before synchronization
111536         if (lockedHeights.length && normalHeights.length) {
111537             lockedView = me.lockedGrid.getView();
111538             normalView = me.normalGrid.getView();
111539             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
111540             normalRowEls = normalView.el.query(normalView.getItemSelector());
111541
111542             // loop thru all of the heights and sync to the other side
111543             for (; i < ln; i++) {
111544                 // ensure both are numbers
111545                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
111546                     if (lockedHeights[i] > normalHeights[i]) {
111547                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
111548                     } else if (lockedHeights[i] < normalHeights[i]) {
111549                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
111550                     }
111551                 }
111552             }
111553
111554             // invalidate the scroller and sync the scrollers
111555             me.normalGrid.invalidateScroller();
111556
111557             // synchronize the view with the scroller, if we have a virtualScrollTop
111558             // then the user is using a PagingScroller
111559             if (vertScroller && vertScroller.setViewScrollTop) {
111560                 vertScroller.setViewScrollTop(me.virtualScrollTop);
111561             } else {
111562                 // We don't use setScrollTop here because if the scrollTop is
111563                 // set to the exact same value some browsers won't fire the scroll
111564                 // event. Instead, we directly set the scrollTop.
111565                 scrollTop = normalView.el.dom.scrollTop;
111566                 normalView.el.dom.scrollTop = scrollTop;
111567                 lockedView.el.dom.scrollTop = scrollTop;
111568             }
111569
111570             // reset the heights
111571             me.lockedHeights = [];
111572             me.normalHeights = [];
111573         }
111574     },
111575
111576     // track when scroller is shown
111577     onScrollerShow: function(scroller, direction) {
111578         if (direction === 'horizontal') {
111579             this.spacerHidden = false;
111580             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
111581         }
111582     },
111583
111584     // track when scroller is hidden
111585     onScrollerHide: function(scroller, direction) {
111586         if (direction === 'horizontal') {
111587             this.spacerHidden = true;
111588             if (this.spacerEl) {
111589                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
111590             }
111591         }
111592     },
111593
111594
111595     // inject Lock and Unlock text
111596     modifyHeaderCt: function() {
111597         var me = this;
111598         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
111599         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
111600     },
111601
111602     onUnlockMenuClick: function() {
111603         this.unlock();
111604     },
111605
111606     onLockMenuClick: function() {
111607         this.lock();
111608     },
111609
111610     getMenuItems: function(locked) {
111611         var me            = this,
111612             unlockText    = me.unlockText,
111613             lockText      = me.lockText,
111614             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
111615             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
111616             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
111617             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
111618
111619         // runs in the scope of headerCt
111620         return function() {
111621             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
111622             o.push('-',{
111623                 cls: unlockCls,
111624                 text: unlockText,
111625                 handler: unlockHandler,
111626                 disabled: !locked
111627             });
111628             o.push({
111629                 cls: lockCls,
111630                 text: lockText,
111631                 handler: lockHandler,
111632                 disabled: locked
111633             });
111634             return o;
111635         };
111636     },
111637
111638     // going from unlocked section to locked
111639     /**
111640      * Locks the activeHeader as determined by which menu is open OR a header
111641      * as specified.
111642      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111643      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
111644      * @private
111645      */
111646     lock: function(activeHd, toIdx) {
111647         var me         = this,
111648             normalGrid = me.normalGrid,
111649             lockedGrid = me.lockedGrid,
111650             normalHCt  = normalGrid.headerCt,
111651             lockedHCt  = lockedGrid.headerCt;
111652
111653         activeHd = activeHd || normalHCt.getMenu().activeHeader;
111654
111655         // if column was previously flexed, get/set current width
111656         // and remove the flex
111657         if (activeHd.flex) {
111658             activeHd.width = activeHd.getWidth();
111659             delete activeHd.flex;
111660         }
111661
111662         normalHCt.remove(activeHd, false);
111663         lockedHCt.suspendLayout = true;
111664         activeHd.locked = true;
111665         if (Ext.isDefined(toIdx)) {
111666             lockedHCt.insert(toIdx, activeHd);
111667         } else {
111668             lockedHCt.add(activeHd);
111669         }
111670         lockedHCt.suspendLayout = false;
111671         me.syncLockedSection();
111672
111673         me.fireEvent('lockcolumn', me, activeHd);
111674     },
111675
111676     syncLockedSection: function() {
111677         var me = this;
111678         me.syncLockedWidth();
111679         me.lockedGrid.getView().refresh();
111680         me.normalGrid.getView().refresh();
111681     },
111682
111683     // adjust the locked section to the width of its respective
111684     // headerCt
111685     syncLockedWidth: function() {
111686         var me = this,
111687             width = me.lockedGrid.headerCt.getFullWidth(true);
111688         me.lockedGrid.setWidth(width+1); // +1 for border pixel
111689         me.doComponentLayout();
111690     },
111691
111692     onLockedHeaderResize: function() {
111693         this.syncLockedWidth();
111694     },
111695
111696     onLockedHeaderHide: function() {
111697         this.syncLockedWidth();
111698     },
111699
111700     onLockedHeaderShow: function() {
111701         this.syncLockedWidth();
111702     },
111703
111704     onLockedHeaderSortChange: function(headerCt, header, sortState) {
111705         if (sortState) {
111706             // no real header, and silence the event so we dont get into an
111707             // infinite loop
111708             this.normalGrid.headerCt.clearOtherSortStates(null, true);
111709         }
111710     },
111711
111712     onNormalHeaderSortChange: function(headerCt, header, sortState) {
111713         if (sortState) {
111714             // no real header, and silence the event so we dont get into an
111715             // infinite loop
111716             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
111717         }
111718     },
111719
111720     // going from locked section to unlocked
111721     /**
111722      * Unlocks the activeHeader as determined by which menu is open OR a header
111723      * as specified.
111724      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111725      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
111726      * @private
111727      */
111728     unlock: function(activeHd, toIdx) {
111729         var me         = this,
111730             normalGrid = me.normalGrid,
111731             lockedGrid = me.lockedGrid,
111732             normalHCt  = normalGrid.headerCt,
111733             lockedHCt  = lockedGrid.headerCt;
111734
111735         if (!Ext.isDefined(toIdx)) {
111736             toIdx = 0;
111737         }
111738         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
111739
111740         lockedHCt.remove(activeHd, false);
111741         me.syncLockedWidth();
111742         me.lockedGrid.getView().refresh();
111743         activeHd.locked = false;
111744         normalHCt.insert(toIdx, activeHd);
111745         me.normalGrid.getView().refresh();
111746
111747         me.fireEvent('unlockcolumn', me, activeHd);
111748     },
111749
111750     applyColumnsState: function (columns) {
111751         var me = this,
111752             lockedGrid = me.lockedGrid,
111753             lockedHeaderCt = lockedGrid.headerCt,
111754             normalHeaderCt = me.normalGrid.headerCt,
111755             lockedCols = lockedHeaderCt.items,
111756             normalCols = normalHeaderCt.items,
111757             existing,
111758             locked = [],
111759             normal = [],
111760             lockedDefault,
111761             lockedWidth = 1;
111762
111763         Ext.each(columns, function (col) {
111764             function matches (item) {
111765                 return item.headerId == col.id;
111766             }
111767
111768             lockedDefault = true;
111769             if (!(existing = lockedCols.findBy(matches))) {
111770                 existing = normalCols.findBy(matches);
111771                 lockedDefault = false;
111772             }
111773
111774             if (existing) {
111775                 if (existing.applyColumnState) {
111776                     existing.applyColumnState(col);
111777                 }
111778                 if (!Ext.isDefined(existing.locked)) {
111779                     existing.locked = lockedDefault;
111780                 }
111781                 if (existing.locked) {
111782                     locked.push(existing);
111783                     if (!existing.hidden && Ext.isNumber(existing.width)) {
111784                         lockedWidth += existing.width;
111785                     }
111786                 } else {
111787                     normal.push(existing);
111788                 }
111789             }
111790         });
111791
111792         // state and config must have the same columns (compare counts for now):
111793         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
111794             lockedHeaderCt.removeAll(false);
111795             normalHeaderCt.removeAll(false);
111796
111797             lockedHeaderCt.add(locked);
111798             normalHeaderCt.add(normal);
111799
111800             lockedGrid.setWidth(lockedWidth);
111801         }
111802     },
111803
111804     getColumnsState: function () {
111805         var me = this,
111806             locked = me.lockedGrid.headerCt.getColumnsState(),
111807             normal = me.normalGrid.headerCt.getColumnsState();
111808
111809         return locked.concat(normal);
111810     },
111811
111812     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
111813     reconfigureLockable: function(store, columns) {
111814         var me = this,
111815             lockedGrid = me.lockedGrid,
111816             normalGrid = me.normalGrid;
111817
111818         if (columns) {
111819             lockedGrid.headerCt.suspendLayout = true;
111820             normalGrid.headerCt.suspendLayout = true;
111821             lockedGrid.headerCt.removeAll();
111822             normalGrid.headerCt.removeAll();
111823
111824             columns = me.processColumns(columns);
111825             lockedGrid.setWidth(columns.lockedWidth);
111826             lockedGrid.headerCt.add(columns.locked);
111827             normalGrid.headerCt.add(columns.normal);
111828         }
111829
111830         if (store) {
111831             store = Ext.data.StoreManager.lookup(store);
111832             me.store = store;
111833             lockedGrid.bindStore(store);
111834             normalGrid.bindStore(store);
111835         } else {
111836             lockedGrid.getView().refresh();
111837             normalGrid.getView().refresh();
111838         }
111839
111840         if (columns) {
111841             lockedGrid.headerCt.suspendLayout = false;
111842             normalGrid.headerCt.suspendLayout = false;
111843             lockedGrid.headerCt.forceComponentLayout();
111844             normalGrid.headerCt.forceComponentLayout();
111845         }
111846     }
111847 });
111848
111849 /**
111850  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
111851  * across different sections.
111852  */
111853 Ext.define('Ext.grid.Scroller', {
111854     extend: 'Ext.Component',
111855     alias: 'widget.gridscroller',
111856     weight: 110,
111857     baseCls: Ext.baseCSSPrefix + 'scroller',
111858     focusable: false,
111859     reservedSpace: 0,
111860
111861     renderTpl: [
111862         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
111863             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
111864         '</div>'
111865     ],
111866
111867     initComponent: function() {
111868         var me       = this,
111869             dock     = me.dock,
111870             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
111871
111872         me.offsets = {bottom: 0};
111873         me.scrollProp = 'scrollTop';
111874         me.vertical = true;
111875         me.sizeProp = 'width';
111876
111877         if (dock === 'top' || dock === 'bottom') {
111878             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
111879             me.sizeProp = 'height';
111880             me.scrollProp = 'scrollLeft';
111881             me.vertical = false;
111882             me.weight += 5;
111883         }
111884
111885         me.cls += (' ' + cls);
111886
111887         Ext.applyIf(me.renderSelectors, {
111888             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
111889             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
111890         });
111891         me.callParent();
111892     },
111893     
111894     ensureDimension: function(){
111895         var me = this,
111896             sizeProp = me.sizeProp;
111897             
111898         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
111899     },
111900
111901     initRenderData: function () {
111902         var me = this,
111903             ret = me.callParent(arguments) || {};
111904
111905         ret.baseId = me.id;
111906
111907         return ret;
111908     },
111909
111910     afterRender: function() {
111911         var me = this;
111912         me.callParent();
111913         
111914         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
111915         Ext.cache[me.el.id].skipGarbageCollection = true;
111916     },
111917
111918     onAdded: function(container) {
111919         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
111920         this.ownerGrid = container;
111921         this.callParent(arguments);
111922     },
111923
111924     getSizeCalculation: function() {
111925         var me     = this,
111926             owner  = me.getPanel(),
111927             width  = 1,
111928             height = 1,
111929             view, tbl;
111930
111931         if (!me.vertical) {
111932             // TODO: Must gravitate to a single region..
111933             // Horizontal scrolling only scrolls virtualized region
111934             var items  = owner.query('tableview'),
111935                 center = items[1] || items[0];
111936
111937             if (!center) {
111938                 return false;
111939             }
111940             // center is not guaranteed to have content, such as when there
111941             // are zero rows in the grid/tree. We read the width from the
111942             // headerCt instead.
111943             width = center.headerCt.getFullWidth();
111944
111945             if (Ext.isIEQuirks) {
111946                 width--;
111947             }
111948         } else {
111949             view = owner.down('tableview:not([lockableInjected])');
111950             if (!view || !view.el) {
111951                 return false;
111952             }
111953             tbl = view.el.child('table', true);
111954             if (!tbl) {
111955                 return false;
111956             }
111957
111958             // needs to also account for header and scroller (if still in picture)
111959             // should calculate from headerCt.
111960             height = tbl.offsetHeight;
111961         }
111962         if (isNaN(width)) {
111963             width = 1;
111964         }
111965         if (isNaN(height)) {
111966             height = 1;
111967         }
111968         return {
111969             width: width,
111970             height: height
111971         };
111972     },
111973
111974     invalidate: function(firstPass) {
111975         var me = this,
111976             stretchEl = me.stretchEl;
111977
111978         if (!stretchEl || !me.ownerCt) {
111979             return;
111980         }
111981
111982         var size  = me.getSizeCalculation(),
111983             scrollEl = me.scrollEl,
111984             elDom = scrollEl.dom,
111985             reservedSpace = me.reservedSpace,
111986             pos,
111987             extra = 5;
111988
111989         if (size) {
111990             stretchEl.setSize(size);
111991
111992             size = me.el.getSize(true);
111993
111994             if (me.vertical) {
111995                 size.width += extra;
111996                 size.height -= reservedSpace;
111997                 pos = 'left';
111998             } else {
111999                 size.width -= reservedSpace;
112000                 size.height += extra;
112001                 pos = 'top';
112002             }
112003
112004             scrollEl.setSize(size);
112005             elDom.style[pos] = (-extra) + 'px';
112006
112007             // BrowserBug: IE7
112008             // This makes the scroller enabled, when initially rendering.
112009             elDom.scrollTop = elDom.scrollTop;
112010         }
112011     },
112012
112013     afterComponentLayout: function() {
112014         this.callParent(arguments);
112015         this.invalidate();
112016     },
112017
112018     restoreScrollPos: function () {
112019         var me = this,
112020             el = this.scrollEl,
112021             elDom = el && el.dom;
112022
112023         if (me._scrollPos !== null && elDom) {
112024             elDom[me.scrollProp] = me._scrollPos;
112025             me._scrollPos = null;
112026         }
112027     },
112028
112029     setReservedSpace: function (reservedSpace) {
112030         var me = this;
112031         if (me.reservedSpace !== reservedSpace) {
112032             me.reservedSpace = reservedSpace;
112033             me.invalidate();
112034         }
112035     },
112036
112037     saveScrollPos: function () {
112038         var me = this,
112039             el = this.scrollEl,
112040             elDom = el && el.dom;
112041
112042         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
112043     },
112044
112045     /**
112046      * Sets the scrollTop and constrains the value between 0 and max.
112047      * @param {Number} scrollTop
112048      * @return {Number} The resulting scrollTop value after being constrained
112049      */
112050     setScrollTop: function(scrollTop) {
112051         var el = this.scrollEl,
112052             elDom = el && el.dom;
112053
112054         if (elDom) {
112055             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
112056         }
112057     },
112058
112059     /**
112060      * Sets the scrollLeft and constrains the value between 0 and max.
112061      * @param {Number} scrollLeft
112062      * @return {Number} The resulting scrollLeft value after being constrained
112063      */
112064     setScrollLeft: function(scrollLeft) {
112065         var el = this.scrollEl,
112066             elDom = el && el.dom;
112067
112068         if (elDom) {
112069             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
112070         }
112071     },
112072
112073     /**
112074      * Scroll by deltaY
112075      * @param {Number} delta
112076      * @return {Number} The resulting scrollTop value
112077      */
112078     scrollByDeltaY: function(delta) {
112079         var el = this.scrollEl,
112080             elDom = el && el.dom;
112081
112082         if (elDom) {
112083             return this.setScrollTop(elDom.scrollTop + delta);
112084         }
112085     },
112086
112087     /**
112088      * Scroll by deltaX
112089      * @param {Number} delta
112090      * @return {Number} The resulting scrollLeft value
112091      */
112092     scrollByDeltaX: function(delta) {
112093         var el = this.scrollEl,
112094             elDom = el && el.dom;
112095
112096         if (elDom) {
112097             return this.setScrollLeft(elDom.scrollLeft + delta);
112098         }
112099     },
112100
112101
112102     /**
112103      * Scroll to the top.
112104      */
112105     scrollToTop : function(){
112106         this.setScrollTop(0);
112107     },
112108
112109     // synchronize the scroller with the bound gridviews
112110     onElScroll: function(event, target) {
112111         this.fireEvent('bodyscroll', event, target);
112112     },
112113
112114     getPanel: function() {
112115         var me = this;
112116         if (!me.panel) {
112117             me.panel = this.up('[scrollerOwner]');
112118         }
112119         return me.panel;
112120     }
112121 });
112122
112123
112124 /**
112125  * @class Ext.grid.PagingScroller
112126  * @extends Ext.grid.Scroller
112127  */
112128 Ext.define('Ext.grid.PagingScroller', {
112129     extend: 'Ext.grid.Scroller',
112130     alias: 'widget.paginggridscroller',
112131     //renderTpl: null,
112132     //tpl: [
112133     //    '<tpl for="pages">',
112134     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
112135     //    '</tpl>'
112136     //],
112137     /**
112138      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
112139      * at what percentage to begin fetching the next page. For example if the pageSize is 100
112140      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
112141      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
112142      */
112143     percentageFromEdge: 0.35,
112144
112145     /**
112146      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
112147      * when scrolling the PagingScrollbar.
112148      */
112149     scrollToLoadBuffer: 200,
112150
112151     activePrefetch: true,
112152
112153     chunkSize: 50,
112154     snapIncrement: 25,
112155
112156     syncScroll: true,
112157
112158     initComponent: function() {
112159         var me = this,
112160             ds = me.store;
112161
112162         ds.on('guaranteedrange', me.onGuaranteedRange, me);
112163         me.callParent(arguments);
112164     },
112165
112166     onGuaranteedRange: function(range, start, end) {
112167         var me = this,
112168             ds = me.store,
112169             rs;
112170         // this should never happen
112171         if (range.length && me.visibleStart < range[0].index) {
112172             return;
112173         }
112174
112175         ds.loadRecords(range);
112176
112177         if (!me.firstLoad) {
112178             if (me.rendered) {
112179                 me.invalidate();
112180             } else {
112181                 me.on('afterrender', me.invalidate, me, {single: true});
112182             }
112183             me.firstLoad = true;
112184         } else {
112185             // adjust to visible
112186             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
112187             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
112188                 me.syncTo();
112189             }
112190         }
112191     },
112192
112193     syncTo: function() {
112194         var me            = this,
112195             pnl           = me.getPanel(),
112196             store         = pnl.store,
112197             scrollerElDom = this.scrollEl.dom,
112198             rowOffset     = me.visibleStart - store.guaranteedStart,
112199             scrollBy      = rowOffset * me.rowHeight,
112200             scrollHeight  = scrollerElDom.scrollHeight,
112201             clientHeight  = scrollerElDom.clientHeight,
112202             scrollTop     = scrollerElDom.scrollTop,
112203             useMaximum;
112204             
112205
112206         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
112207         // Instead we are using offsetHeight and hardcoding borders
112208         if (Ext.isIE9 && Ext.isStrict) {
112209             clientHeight = scrollerElDom.offsetHeight + 2;
112210         }
112211
112212         // This should always be zero or greater than zero but staying
112213         // safe and less than 0 we'll scroll to the bottom.
112214         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
112215         this.setViewScrollTop(scrollBy, useMaximum);
112216     },
112217
112218     getPageData : function(){
112219         var panel = this.getPanel(),
112220             store = panel.store,
112221             totalCount = store.getTotalCount();
112222
112223         return {
112224             total : totalCount,
112225             currentPage : store.currentPage,
112226             pageCount: Math.ceil(totalCount / store.pageSize),
112227             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
112228             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
112229         };
112230     },
112231
112232     onElScroll: function(e, t) {
112233         var me = this,
112234             panel = me.getPanel(),
112235             store = panel.store,
112236             pageSize = store.pageSize,
112237             guaranteedStart = store.guaranteedStart,
112238             guaranteedEnd = store.guaranteedEnd,
112239             totalCount = store.getTotalCount(),
112240             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
112241             position = t.scrollTop,
112242             visibleStart = Math.floor(position / me.rowHeight),
112243             view = panel.down('tableview'),
112244             viewEl = view.el,
112245             visibleHeight = viewEl.getHeight(),
112246             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
112247             visibleEnd = visibleStart + visibleAhead,
112248             prevPage = Math.floor(visibleStart / pageSize),
112249             nextPage = Math.floor(visibleEnd / pageSize) + 2,
112250             lastPage = Math.ceil(totalCount / pageSize),
112251             snap = me.snapIncrement,
112252             requestStart = Math.floor(visibleStart / snap) * snap,
112253             requestEnd = requestStart + pageSize - 1,
112254             activePrefetch = me.activePrefetch;
112255
112256         me.visibleStart = visibleStart;
112257         me.visibleEnd = visibleEnd;
112258         
112259         
112260         me.syncScroll = true;
112261         if (totalCount >= pageSize) {
112262             // end of request was past what the total is, grab from the end back a pageSize
112263             if (requestEnd > totalCount - 1) {
112264                 me.cancelLoad();
112265                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
112266                     me.syncScroll = true;
112267                 }
112268                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
112269             // Out of range, need to reset the current data set
112270             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
112271                 if (visibleStart <= guaranteedStart) {
112272                     // need to scroll up
112273                     requestStart -= snap;
112274                     requestEnd -= snap;
112275                     
112276                     if (requestStart < 0) {
112277                         requestStart = 0;
112278                         requestEnd = pageSize;
112279                     }
112280                 }
112281                 if (store.rangeSatisfied(requestStart, requestEnd)) {
112282                     me.cancelLoad();
112283                     store.guaranteeRange(requestStart, requestEnd);
112284                 } else {
112285                     store.mask();
112286                     me.attemptLoad(requestStart, requestEnd);
112287                 }
112288                 // dont sync the scroll view immediately, sync after the range has been guaranteed
112289                 me.syncScroll = false;
112290             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
112291                 me.syncScroll = true;
112292                 store.prefetchPage(prevPage);
112293             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
112294                 me.syncScroll = true;
112295                 store.prefetchPage(nextPage);
112296             }
112297         }
112298
112299         if (me.syncScroll) {
112300             me.syncTo();
112301         }
112302     },
112303
112304     getSizeCalculation: function() {
112305         // Use the direct ownerCt here rather than the scrollerOwner
112306         // because we are calculating widths/heights.
112307         var me     = this,
112308             owner  = me.ownerGrid,
112309             view   = owner.getView(),
112310             store  = me.store,
112311             dock   = me.dock,
112312             elDom  = me.el.dom,
112313             width  = 1,
112314             height = 1;
112315
112316         if (!me.rowHeight) {
112317             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
112318         }
112319
112320         // If the Store is *locally* filtered, use the filtered count from getCount.
112321         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
112322
112323         if (isNaN(width)) {
112324             width = 1;
112325         }
112326         if (isNaN(height)) {
112327             height = 1;
112328         }
112329         return {
112330             width: width,
112331             height: height
112332         };
112333     },
112334
112335     attemptLoad: function(start, end) {
112336         var me = this;
112337         if (!me.loadTask) {
112338             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
112339         }
112340         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
112341     },
112342
112343     cancelLoad: function() {
112344         if (this.loadTask) {
112345             this.loadTask.cancel();
112346         }
112347     },
112348
112349     doAttemptLoad:  function(start, end) {
112350         var store = this.getPanel().store;
112351         store.guaranteeRange(start, end);
112352     },
112353
112354     setViewScrollTop: function(scrollTop, useMax) {
112355         var me = this,
112356             owner = me.getPanel(),
112357             items = owner.query('tableview'),
112358             i = 0,
112359             len = items.length,
112360             center,
112361             centerEl,
112362             calcScrollTop,
112363             maxScrollTop,
112364             scrollerElDom = me.el.dom;
112365
112366         owner.virtualScrollTop = scrollTop;
112367
112368         center = items[1] || items[0];
112369         centerEl = center.el.dom;
112370
112371         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
112372         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
112373         if (useMax) {
112374             calcScrollTop = maxScrollTop;
112375         }
112376         if (calcScrollTop > maxScrollTop) {
112377             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
112378             return;
112379             // calcScrollTop = maxScrollTop;
112380         }
112381         for (; i < len; i++) {
112382             items[i].el.dom.scrollTop = calcScrollTop;
112383         }
112384     }
112385 });
112386
112387 /**
112388  * @author Nicolas Ferrero
112389  *
112390  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
112391  *
112392  * TablePanel aggregates:
112393  *
112394  *  - a Selection Model
112395  *  - a View
112396  *  - a Store
112397  *  - Scrollers
112398  *  - Ext.grid.header.Container
112399  */
112400 Ext.define('Ext.panel.Table', {
112401     extend: 'Ext.panel.Panel',
112402
112403     alias: 'widget.tablepanel',
112404
112405     uses: [
112406         'Ext.selection.RowModel',
112407         'Ext.grid.Scroller',
112408         'Ext.grid.header.Container',
112409         'Ext.grid.Lockable'
112410     ],
112411
112412     extraBaseCls: Ext.baseCSSPrefix + 'grid',
112413     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
112414
112415     layout: 'fit',
112416     /**
112417      * @property {Boolean} hasView
112418      * True to indicate that a view has been injected into the panel.
112419      */
112420     hasView: false,
112421
112422     // each panel should dictate what viewType and selType to use
112423     /**
112424      * @cfg {String} viewType
112425      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
112426      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
112427      */
112428     viewType: null,
112429
112430     /**
112431      * @cfg {Object} viewConfig
112432      * A config object that will be applied to the grid's UI view. Any of the config options available for
112433      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
112434      */
112435
112436     /**
112437      * @cfg {Ext.view.Table} view
112438      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
112439      * view (instead of creating an entire View instance).
112440      */
112441
112442     /**
112443      * @cfg {String} selType
112444      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
112445      * a config object or nothing at all given in {@link #selModel} config.
112446      */
112447     selType: 'rowmodel',
112448
112449     /**
112450      * @cfg {Ext.selection.Model/Object} selModel
112451      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
112452      * config option determines to which type of selection model this config is applied.
112453      */
112454
112455     /**
112456      * @cfg {Boolean} multiSelect
112457      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112458      */
112459
112460     /**
112461      * @cfg {Boolean} simpleSelect
112462      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112463      */
112464
112465     /**
112466      * @cfg {Ext.data.Store} store (required)
112467      * The {@link Ext.data.Store Store} the grid should use as its data source.
112468      */
112469
112470     /**
112471      * @cfg {Number} scrollDelta
112472      * Number of pixels to scroll when scrolling with mousewheel.
112473      */
112474     scrollDelta: 40,
112475
112476     /**
112477      * @cfg {String/Boolean} scroll
112478      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
112479      * True implies 'both'. False implies 'none'.
112480      */
112481     scroll: true,
112482
112483     /**
112484      * @cfg {Ext.grid.column.Column[]} columns
112485      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
112486      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
112487      * column comes from.
112488      */
112489
112490     /**
112491      * @cfg {Boolean} forceFit
112492      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
112493      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
112494      * content width is used.
112495      */
112496
112497     /**
112498      * @cfg {Ext.grid.feature.Feature[]} features
112499      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
112500      */
112501
112502     /**
112503      * @cfg {Boolean} [hideHeaders=false]
112504      * True to hide column headers.
112505      */
112506
112507     /**
112508      * @cfg {Boolean} deferRowRender
112509      * Defaults to true to enable deferred row rendering.
112510      *
112511      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
112512      * that layouts with GridPanels appear, and lay out more quickly.
112513      */
112514
112515      deferRowRender: true,
112516      
112517     /**
112518      * @cfg {Boolean} sortableColumns
112519      * False to disable column sorting via clicking the header and via the Sorting menu items.
112520      */
112521     sortableColumns: true,
112522
112523     /**
112524      * @cfg {Boolean} [enableLocking=false]
112525      * True to enable locking support for this grid. Alternatively, locking will also be automatically
112526      * enabled if any of the columns in the column configuration contain the locked config option.
112527      */
112528     enableLocking: false,
112529
112530     verticalScrollDock: 'right',
112531     verticalScrollerType: 'gridscroller',
112532
112533     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
112534     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
112535
112536     // private property used to determine where to go down to find views
112537     // this is here to support locking.
112538     scrollerOwner: true,
112539
112540     invalidateScrollerOnRefresh: true,
112541
112542     /**
112543      * @cfg {Boolean} enableColumnMove
112544      * False to disable column dragging within this grid.
112545      */
112546     enableColumnMove: true,
112547
112548     /**
112549      * @cfg {Boolean} enableColumnResize
112550      * False to disable column resizing within this grid.
112551      */
112552     enableColumnResize: true,
112553
112554     /**
112555      * @cfg {Boolean} enableColumnHide
112556      * False to disable column hiding within this grid.
112557      */
112558     enableColumnHide: true,
112559
112560     initComponent: function() {
112561
112562         var me          = this,
112563             scroll      = me.scroll,
112564             vertical    = false,
112565             horizontal  = false,
112566             headerCtCfg = me.columns || me.colModel,
112567             i           = 0,
112568             view,
112569             border = me.border;
112570
112571         if (me.hideHeaders) {
112572             border = false;
112573         }
112574
112575         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
112576         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
112577
112578         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
112579         // Either way, we extract a columns property referencing an array of Column definitions.
112580         if (headerCtCfg instanceof Ext.grid.header.Container) {
112581             me.headerCt = headerCtCfg;
112582             me.headerCt.border = border;
112583             me.columns = me.headerCt.items.items;
112584         } else {
112585             if (Ext.isArray(headerCtCfg)) {
112586                 headerCtCfg = {
112587                     items: headerCtCfg,
112588                     border: border
112589                 };
112590             }
112591             Ext.apply(headerCtCfg, {
112592                 forceFit: me.forceFit,
112593                 sortable: me.sortableColumns,
112594                 enableColumnMove: me.enableColumnMove,
112595                 enableColumnResize: me.enableColumnResize,
112596                 enableColumnHide: me.enableColumnHide,
112597                 border:  border
112598             });
112599             me.columns = headerCtCfg.items;
112600
112601              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
112602              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
112603              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
112604                  me.self.mixin('lockable', Ext.grid.Lockable);
112605                  me.injectLockable();
112606              }
112607         }
112608
112609         me.addEvents(
112610             /**
112611              * @event reconfigure
112612              * Fires after a reconfigure.
112613              * @param {Ext.panel.Table} this
112614              */
112615             'reconfigure',
112616             /**
112617              * @event viewready
112618              * Fires when the grid view is available (use this for selecting a default row).
112619              * @param {Ext.panel.Table} this
112620              */
112621             'viewready',
112622             /**
112623              * @event scrollerhide
112624              * Fires when a scroller is hidden.
112625              * @param {Ext.grid.Scroller} scroller
112626              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112627              */
112628             'scrollerhide',
112629             /**
112630              * @event scrollershow
112631              * Fires when a scroller is shown.
112632              * @param {Ext.grid.Scroller} scroller
112633              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112634              */
112635             'scrollershow'
112636         );
112637
112638         me.bodyCls = me.bodyCls || '';
112639         me.bodyCls += (' ' + me.extraBodyCls);
112640         
112641         me.cls = me.cls || '';
112642         me.cls += (' ' + me.extraBaseCls);
112643
112644         // autoScroll is not a valid configuration
112645         delete me.autoScroll;
112646
112647         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
112648         // 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.
112649         if (!me.hasView) {
112650
112651             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
112652             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
112653             if (!me.headerCt) {
112654                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
112655             }
112656
112657             // Extract the array of Column objects
112658             me.columns = me.headerCt.items.items;
112659
112660             if (me.hideHeaders) {
112661                 me.headerCt.height = 0;
112662                 me.headerCt.border = false;
112663                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
112664                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
112665                 // IE Quirks Mode fix
112666                 // If hidden configuration option was used, several layout calculations will be bypassed.
112667                 if (Ext.isIEQuirks) {
112668                     me.headerCt.style = {
112669                         display: 'none'
112670                     };
112671                 }
112672             }
112673
112674             // turn both on.
112675             if (scroll === true || scroll === 'both') {
112676                 vertical = horizontal = true;
112677             } else if (scroll === 'horizontal') {
112678                 horizontal = true;
112679             } else if (scroll === 'vertical') {
112680                 vertical = true;
112681             // All other values become 'none' or false.
112682             } else {
112683                 me.headerCt.availableSpaceOffset = 0;
112684             }
112685
112686             if (vertical) {
112687                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
112688                 me.mon(me.verticalScroller, {
112689                     bodyscroll: me.onVerticalScroll,
112690                     scope: me
112691                 });
112692             }
112693
112694             if (horizontal) {
112695                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
112696                 me.mon(me.horizontalScroller, {
112697                     bodyscroll: me.onHorizontalScroll,
112698                     scope: me
112699                 });
112700             }
112701
112702             me.headerCt.on('resize', me.onHeaderResize, me);
112703             me.relayHeaderCtEvents(me.headerCt);
112704             me.features = me.features || [];
112705             if (!Ext.isArray(me.features)) {
112706                 me.features = [me.features];
112707             }
112708             me.dockedItems = me.dockedItems || [];
112709             me.dockedItems.unshift(me.headerCt);
112710             me.viewConfig = me.viewConfig || {};
112711             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
112712
112713             // AbstractDataView will look up a Store configured as an object
112714             // getView converts viewConfig into a View instance
112715             view = me.getView();
112716
112717             view.on({
112718                 afterrender: function () {
112719                     // hijack the view el's scroll method
112720                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
112721                     // We use to listen to document.body wheel events, but that's a
112722                     // little much. We scope just to the view now.
112723                     me.mon(view.el, {
112724                         mousewheel: me.onMouseWheel,
112725                         scope: me
112726                     });
112727                 },
112728                 single: true
112729             });
112730             me.items = [view];
112731             me.hasView = true;
112732
112733             me.mon(view.store, {
112734                 load: me.onStoreLoad,
112735                 scope: me
112736             });
112737             me.mon(view, {
112738                 viewReady: me.onViewReady,
112739                 resize: me.onViewResize,
112740                 refresh: {
112741                     fn: me.onViewRefresh,
112742                     scope: me,
112743                     buffer: 50
112744                 },
112745                 scope: me
112746             });
112747             this.relayEvents(view, [
112748                 /**
112749                  * @event beforeitemmousedown
112750                  * @alias Ext.view.View#beforeitemmousedown
112751                  */
112752                 'beforeitemmousedown',
112753                 /**
112754                  * @event beforeitemmouseup
112755                  * @alias Ext.view.View#beforeitemmouseup
112756                  */
112757                 'beforeitemmouseup',
112758                 /**
112759                  * @event beforeitemmouseenter
112760                  * @alias Ext.view.View#beforeitemmouseenter
112761                  */
112762                 'beforeitemmouseenter',
112763                 /**
112764                  * @event beforeitemmouseleave
112765                  * @alias Ext.view.View#beforeitemmouseleave
112766                  */
112767                 'beforeitemmouseleave',
112768                 /**
112769                  * @event beforeitemclick
112770                  * @alias Ext.view.View#beforeitemclick
112771                  */
112772                 'beforeitemclick',
112773                 /**
112774                  * @event beforeitemdblclick
112775                  * @alias Ext.view.View#beforeitemdblclick
112776                  */
112777                 'beforeitemdblclick',
112778                 /**
112779                  * @event beforeitemcontextmenu
112780                  * @alias Ext.view.View#beforeitemcontextmenu
112781                  */
112782                 'beforeitemcontextmenu',
112783                 /**
112784                  * @event itemmousedown
112785                  * @alias Ext.view.View#itemmousedown
112786                  */
112787                 'itemmousedown',
112788                 /**
112789                  * @event itemmouseup
112790                  * @alias Ext.view.View#itemmouseup
112791                  */
112792                 'itemmouseup',
112793                 /**
112794                  * @event itemmouseenter
112795                  * @alias Ext.view.View#itemmouseenter
112796                  */
112797                 'itemmouseenter',
112798                 /**
112799                  * @event itemmouseleave
112800                  * @alias Ext.view.View#itemmouseleave
112801                  */
112802                 'itemmouseleave',
112803                 /**
112804                  * @event itemclick
112805                  * @alias Ext.view.View#itemclick
112806                  */
112807                 'itemclick',
112808                 /**
112809                  * @event itemdblclick
112810                  * @alias Ext.view.View#itemdblclick
112811                  */
112812                 'itemdblclick',
112813                 /**
112814                  * @event itemcontextmenu
112815                  * @alias Ext.view.View#itemcontextmenu
112816                  */
112817                 'itemcontextmenu',
112818                 /**
112819                  * @event beforecontainermousedown
112820                  * @alias Ext.view.View#beforecontainermousedown
112821                  */
112822                 'beforecontainermousedown',
112823                 /**
112824                  * @event beforecontainermouseup
112825                  * @alias Ext.view.View#beforecontainermouseup
112826                  */
112827                 'beforecontainermouseup',
112828                 /**
112829                  * @event beforecontainermouseover
112830                  * @alias Ext.view.View#beforecontainermouseover
112831                  */
112832                 'beforecontainermouseover',
112833                 /**
112834                  * @event beforecontainermouseout
112835                  * @alias Ext.view.View#beforecontainermouseout
112836                  */
112837                 'beforecontainermouseout',
112838                 /**
112839                  * @event beforecontainerclick
112840                  * @alias Ext.view.View#beforecontainerclick
112841                  */
112842                 'beforecontainerclick',
112843                 /**
112844                  * @event beforecontainerdblclick
112845                  * @alias Ext.view.View#beforecontainerdblclick
112846                  */
112847                 'beforecontainerdblclick',
112848                 /**
112849                  * @event beforecontainercontextmenu
112850                  * @alias Ext.view.View#beforecontainercontextmenu
112851                  */
112852                 'beforecontainercontextmenu',
112853                 /**
112854                  * @event containermouseup
112855                  * @alias Ext.view.View#containermouseup
112856                  */
112857                 'containermouseup',
112858                 /**
112859                  * @event containermouseover
112860                  * @alias Ext.view.View#containermouseover
112861                  */
112862                 'containermouseover',
112863                 /**
112864                  * @event containermouseout
112865                  * @alias Ext.view.View#containermouseout
112866                  */
112867                 'containermouseout',
112868                 /**
112869                  * @event containerclick
112870                  * @alias Ext.view.View#containerclick
112871                  */
112872                 'containerclick',
112873                 /**
112874                  * @event containerdblclick
112875                  * @alias Ext.view.View#containerdblclick
112876                  */
112877                 'containerdblclick',
112878                 /**
112879                  * @event containercontextmenu
112880                  * @alias Ext.view.View#containercontextmenu
112881                  */
112882                 'containercontextmenu',
112883                 /**
112884                  * @event selectionchange
112885                  * @alias Ext.selection.Model#selectionchange
112886                  */
112887                 'selectionchange',
112888                 /**
112889                  * @event beforeselect
112890                  * @alias Ext.selection.RowModel#beforeselect
112891                  */
112892                 'beforeselect',
112893                 /**
112894                  * @event select
112895                  * @alias Ext.selection.RowModel#select
112896                  */
112897                 'select',
112898                 /**
112899                  * @event beforedeselect
112900                  * @alias Ext.selection.RowModel#beforedeselect
112901                  */
112902                 'beforedeselect',
112903                 /**
112904                  * @event deselect
112905                  * @alias Ext.selection.RowModel#deselect
112906                  */
112907                 'deselect'
112908             ]);
112909         }
112910
112911         me.callParent(arguments);
112912     },
112913     
112914     onRender: function(){
112915         var vScroll = this.verticalScroller,
112916             hScroll = this.horizontalScroller;
112917
112918         if (vScroll) {
112919             vScroll.ensureDimension();
112920         }
112921         if (hScroll) {
112922             hScroll.ensureDimension();
112923         }
112924         this.callParent(arguments);    
112925     },
112926
112927     // state management
112928     initStateEvents: function(){
112929         var events = this.stateEvents;
112930         // push on stateEvents if they don't exist
112931         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
112932             if (Ext.Array.indexOf(events, event)) {
112933                 events.push(event);
112934             }
112935         });
112936         this.callParent();
112937     },
112938
112939     /**
112940      * Returns the horizontal scroller config.
112941      */
112942     initHorizontalScroller: function () {
112943         var me = this,
112944             ret = {
112945                 xtype: 'gridscroller',
112946                 dock: 'bottom',
112947                 section: me,
112948                 store: me.store
112949             };
112950
112951         return ret;
112952     },
112953
112954     /**
112955      * Returns the vertical scroller config.
112956      */
112957     initVerticalScroller: function () {
112958         var me = this,
112959             ret = me.verticalScroller || {};
112960
112961         Ext.applyIf(ret, {
112962             xtype: me.verticalScrollerType,
112963             dock: me.verticalScrollDock,
112964             store: me.store
112965         });
112966
112967         return ret;
112968     },
112969
112970     relayHeaderCtEvents: function (headerCt) {
112971         this.relayEvents(headerCt, [
112972             /**
112973              * @event columnresize
112974              * @alias Ext.grid.header.Container#columnresize
112975              */
112976             'columnresize',
112977             /**
112978              * @event columnmove
112979              * @alias Ext.grid.header.Container#columnmove
112980              */
112981             'columnmove',
112982             /**
112983              * @event columnhide
112984              * @alias Ext.grid.header.Container#columnhide
112985              */
112986             'columnhide',
112987             /**
112988              * @event columnshow
112989              * @alias Ext.grid.header.Container#columnshow
112990              */
112991             'columnshow',
112992             /**
112993              * @event sortchange
112994              * @alias Ext.grid.header.Container#sortchange
112995              */
112996             'sortchange'
112997         ]);
112998     },
112999
113000     getState: function(){
113001         var me = this,
113002             state = me.callParent(),
113003             sorter = me.store.sorters.first();
113004
113005         state.columns = (me.headerCt || me).getColumnsState();
113006
113007         if (sorter) {
113008             state.sort = {
113009                 property: sorter.property,
113010                 direction: sorter.direction
113011             };
113012         }
113013
113014         return state;
113015     },
113016
113017     applyState: function(state) {
113018         var me = this,
113019             sorter = state.sort,
113020             store = me.store,
113021             columns = state.columns;
113022
113023         delete state.columns;
113024
113025         // Ensure superclass has applied *its* state.
113026         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
113027         me.callParent(arguments);
113028
113029         if (columns) {
113030             (me.headerCt || me).applyColumnsState(columns);
113031         }
113032
113033         if (sorter) {
113034             if (store.remoteSort) {
113035                 store.sorters.add(Ext.create('Ext.util.Sorter', {
113036                     property: sorter.property,
113037                     direction: sorter.direction
113038                 }));
113039             }
113040             else {
113041                 store.sort(sorter.property, sorter.direction);
113042             }
113043         }
113044     },
113045
113046     /**
113047      * Returns the store associated with this Panel.
113048      * @return {Ext.data.Store} The store
113049      */
113050     getStore: function(){
113051         return this.store;
113052     },
113053
113054     /**
113055      * Gets the view for this panel.
113056      * @return {Ext.view.Table}
113057      */
113058     getView: function() {
113059         var me = this,
113060             sm;
113061
113062         if (!me.view) {
113063             sm = me.getSelectionModel();
113064             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
113065                 deferInitialRefresh: me.deferRowRender,
113066                 xtype: me.viewType,
113067                 store: me.store,
113068                 headerCt: me.headerCt,
113069                 selModel: sm,
113070                 features: me.features,
113071                 panel: me
113072             }));
113073             me.mon(me.view, {
113074                 uievent: me.processEvent,
113075                 scope: me
113076             });
113077             sm.view = me.view;
113078             me.headerCt.view = me.view;
113079             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
113080         }
113081         return me.view;
113082     },
113083
113084     /**
113085      * @private
113086      * @override
113087      * autoScroll is never valid for all classes which extend TablePanel.
113088      */
113089     setAutoScroll: Ext.emptyFn,
113090
113091     // This method hijacks Ext.view.Table's el scroll method.
113092     // This enables us to keep the virtualized scrollbars in sync
113093     // with the view. It currently does NOT support animation.
113094     elScroll: function(direction, distance, animate) {
113095         var me = this,
113096             scroller;
113097
113098         if (direction === "up" || direction === "left") {
113099             distance = -distance;
113100         }
113101         
113102         if (direction === "down" || direction === "up") {
113103             scroller = me.getVerticalScroller();
113104             
113105             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
113106             if (scroller) {
113107                 scroller.scrollByDeltaY(distance);
113108             }
113109         } else {
113110             scroller = me.getHorizontalScroller();
113111             
113112             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
113113             if (scroller) {
113114                 scroller.scrollByDeltaX(distance);
113115             }
113116         }
113117     },
113118
113119     /**
113120      * @private
113121      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
113122      * @param {String} type Event type, eg 'click'
113123      * @param {Ext.view.Table} view TableView Component
113124      * @param {HTMLElement} cell Cell HtmlElement the event took place within
113125      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
113126      * @param {Number} cellIndex Cell index within the row
113127      * @param {Ext.EventObject} e Original event
113128      */
113129     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
113130         var me = this,
113131             header;
113132
113133         if (cellIndex !== -1) {
113134             header = me.headerCt.getGridColumns()[cellIndex];
113135             return header.processEvent.apply(header, arguments);
113136         }
113137     },
113138
113139     /**
113140      * Requests a recalculation of scrollbars and puts them in if they are needed.
113141      */
113142     determineScrollbars: function() {
113143         // Set a flag so that afterComponentLayout does not recurse back into here.
113144         if (this.determineScrollbarsRunning) {
113145             return;
113146         }
113147         this.determineScrollbarsRunning = true;
113148         var me = this,
113149             view = me.view,
113150             box,
113151             tableEl,
113152             scrollWidth,
113153             clientWidth,
113154             scrollHeight,
113155             clientHeight,
113156             verticalScroller = me.verticalScroller,
113157             horizontalScroller = me.horizontalScroller,
113158             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
113159                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
113160             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
113161
113162         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
113163         if (!me.collapsed && view && view.viewReady) {
113164
113165             // Calculate maximum, *scrollbarless* space which the view has available.
113166             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
113167             box = view.el.getSize();
113168
113169             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
113170             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
113171
113172             // Calculate the width of the scrolling block
113173             // There will never be a horizontal scrollbar if all columns are flexed.
113174
113175             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
113176
113177             // Calculate the height of the scrolling block
113178             if (verticalScroller && verticalScroller.el) {
113179                 scrollHeight = verticalScroller.getSizeCalculation().height;
113180             } else {
113181                 tableEl = view.el.child('table', true);
113182                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
113183             }
113184
113185             // View is too high.
113186             // Definitely need a vertical scrollbar
113187             if (scrollHeight > clientHeight) {
113188                 reqScrollbars = 1;
113189
113190                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
113191                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
113192                     reqScrollbars = 3;
113193                 }
113194             }
113195
113196             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
113197             else {
113198                 // View is too wide.
113199                 // Definitely need a horizontal scrollbar
113200                 if (scrollWidth > clientWidth) {
113201                     reqScrollbars = 2;
113202
113203                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
113204                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
113205                         reqScrollbars = 3;
113206                     }
113207                 }
113208             }
113209
113210             // If scrollbar requirements have changed, change 'em...
113211             if (reqScrollbars !== curScrollbars) {
113212
113213                 // Suspend component layout while we add/remove the docked scrollers
113214                 me.suspendLayout = true;
113215                 if (reqScrollbars & 1) {
113216                     me.showVerticalScroller();
113217                 } else {
113218                     me.hideVerticalScroller();
113219                 }
113220                 if (reqScrollbars & 2) {
113221                     me.showHorizontalScroller();
113222                 } else {
113223                     me.hideHorizontalScroller();
113224                 }
113225                 me.suspendLayout = false;
113226
113227                 // Lay out the Component.
113228                 me.doComponentLayout();
113229                 // Lay out me.items
113230                 me.getLayout().layout();
113231             }
113232         }
113233         delete me.determineScrollbarsRunning;
113234     },
113235
113236     onViewResize: function() {
113237         this.determineScrollbars();
113238     },
113239
113240     afterComponentLayout: function() {
113241         this.callParent(arguments);
113242         this.determineScrollbars();
113243         this.invalidateScroller();
113244     },
113245
113246     onHeaderResize: function() {
113247         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
113248             this.determineScrollbars();
113249             this.invalidateScroller();
113250         }
113251     },
113252
113253     afterCollapse: function() {
113254         var me = this;
113255         if (me.verticalScroller) {
113256             me.verticalScroller.saveScrollPos();
113257         }
113258         if (me.horizontalScroller) {
113259             me.horizontalScroller.saveScrollPos();
113260         }
113261         me.callParent(arguments);
113262     },
113263
113264     afterExpand: function() {
113265         var me = this;
113266         me.callParent(arguments);
113267         if (me.verticalScroller) {
113268             me.verticalScroller.restoreScrollPos();
113269         }
113270         if (me.horizontalScroller) {
113271             me.horizontalScroller.restoreScrollPos();
113272         }
113273     },
113274
113275     /**
113276      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
113277      */
113278     hideHorizontalScroller: function() {
113279         var me = this;
113280
113281         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
113282             me.verticalScroller.setReservedSpace(0);
113283             me.removeDocked(me.horizontalScroller, false);
113284             me.removeCls(me.horizontalScrollerPresentCls);
113285             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
113286         }
113287
113288     },
113289
113290     /**
113291      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
113292      */
113293     showHorizontalScroller: function() {
113294         var me = this;
113295
113296         if (me.verticalScroller) {
113297             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
113298         }
113299         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
113300             me.addDocked(me.horizontalScroller);
113301             me.addCls(me.horizontalScrollerPresentCls);
113302             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
113303         }
113304     },
113305
113306     /**
113307      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
113308      */
113309     hideVerticalScroller: function() {
113310         var me = this;
113311
113312         me.setHeaderReserveOffset(false);
113313         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
113314             me.removeDocked(me.verticalScroller, false);
113315             me.removeCls(me.verticalScrollerPresentCls);
113316             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
113317         }
113318     },
113319
113320     /**
113321      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
113322      */
113323     showVerticalScroller: function() {
113324         var me = this;
113325
113326         me.setHeaderReserveOffset(true);
113327         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
113328             me.addDocked(me.verticalScroller);
113329             me.addCls(me.verticalScrollerPresentCls);
113330             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
113331         }
113332     },
113333
113334     setHeaderReserveOffset: function (reserveOffset) {
113335         var headerCt = this.headerCt,
113336             layout = headerCt.layout;
113337
113338         // only trigger a layout when reserveOffset is changing
113339         if (layout && layout.reserveOffset !== reserveOffset) {
113340             layout.reserveOffset = reserveOffset;
113341             if (!this.suspendLayout) {
113342                 headerCt.doLayout();
113343             }
113344         }
113345     },
113346
113347     /**
113348      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
113349      */
113350     invalidateScroller: function() {
113351         var me = this,
113352             vScroll = me.verticalScroller,
113353             hScroll = me.horizontalScroller;
113354
113355         if (vScroll) {
113356             vScroll.invalidate();
113357         }
113358         if (hScroll) {
113359             hScroll.invalidate();
113360         }
113361     },
113362
113363     // refresh the view when a header moves
113364     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
113365         this.view.refresh();
113366     },
113367
113368     // Section onHeaderHide is invoked after view.
113369     onHeaderHide: function(headerCt, header) {
113370         this.invalidateScroller();
113371     },
113372
113373     onHeaderShow: function(headerCt, header) {
113374         this.invalidateScroller();
113375     },
113376
113377     getVerticalScroller: function() {
113378         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
113379     },
113380
113381     getHorizontalScroller: function() {
113382         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
113383     },
113384
113385     onMouseWheel: function(e) {
113386         var me = this,
113387             vertScroller = me.getVerticalScroller(),
113388             horizScroller = me.getHorizontalScroller(),
113389             scrollDelta = -me.scrollDelta,
113390             deltas = e.getWheelDeltas(),
113391             deltaX = scrollDelta * deltas.x,
113392             deltaY = scrollDelta * deltas.y,
113393             vertScrollerEl, horizScrollerEl,
113394             vertScrollerElDom, horizScrollerElDom,
113395             horizontalCanScrollLeft, horizontalCanScrollRight,
113396             verticalCanScrollDown, verticalCanScrollUp;
113397
113398         // calculate whether or not both scrollbars can scroll right/left and up/down
113399         if (horizScroller) {
113400             horizScrollerEl = horizScroller.scrollEl;
113401             if (horizScrollerEl) {
113402                 horizScrollerElDom = horizScrollerEl.dom;
113403                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
113404                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
113405             }
113406         }
113407         if (vertScroller) {
113408             vertScrollerEl = vertScroller.scrollEl;
113409             if (vertScrollerEl) {
113410                 vertScrollerElDom = vertScrollerEl.dom;
113411                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
113412                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
113413             }
113414         }
113415
113416         if (horizScroller) {
113417             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
113418                 e.stopEvent();
113419                 horizScroller.scrollByDeltaX(deltaX);
113420             }
113421         }
113422         if (vertScroller) {
113423             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
113424                 e.stopEvent();
113425                 vertScroller.scrollByDeltaY(deltaY);
113426             }
113427         }
113428     },
113429
113430     /**
113431      * @private
113432      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
113433      */
113434     onViewReady: function() {
113435         var me = this;
113436         me.fireEvent('viewready', me);
113437         if (me.deferRowRender) {
113438             me.determineScrollbars();
113439             me.invalidateScroller();
113440         }
113441     },
113442
113443     /**
113444      * @private
113445      * Determines and invalidates scrollers on view refresh
113446      */
113447     onViewRefresh: function() {
113448         var me = this;
113449
113450         // Refresh *during* render must be ignored.
113451         if (!me.rendering) {
113452             this.determineScrollbars();
113453             if (this.invalidateScrollerOnRefresh) {
113454                 this.invalidateScroller();
113455             }
113456         }
113457     },
113458
113459     /**
113460      * Sets the scrollTop of the TablePanel.
113461      * @param {Number} top
113462      */
113463     setScrollTop: function(top) {
113464         var me               = this,
113465             rootCmp          = me.getScrollerOwner(),
113466             verticalScroller = me.getVerticalScroller();
113467
113468         rootCmp.virtualScrollTop = top;
113469         if (verticalScroller) {
113470             verticalScroller.setScrollTop(top);
113471         }
113472     },
113473
113474     getScrollerOwner: function() {
113475         var rootCmp = this;
113476         if (!this.scrollerOwner) {
113477             rootCmp = this.up('[scrollerOwner]');
113478         }
113479         return rootCmp;
113480     },
113481
113482     /**
113483      * Scrolls the TablePanel by deltaY
113484      * @param {Number} deltaY
113485      */
113486     scrollByDeltaY: function(deltaY) {
113487         var verticalScroller = this.getVerticalScroller();
113488
113489         if (verticalScroller) {
113490             verticalScroller.scrollByDeltaY(deltaY);
113491         }
113492     },
113493
113494     /**
113495      * Scrolls the TablePanel by deltaX
113496      * @param {Number} deltaX
113497      */
113498     scrollByDeltaX: function(deltaX) {
113499         var horizontalScroller = this.getHorizontalScroller();
113500
113501         if (horizontalScroller) {
113502             horizontalScroller.scrollByDeltaX(deltaX);
113503         }
113504     },
113505
113506     /**
113507      * Gets left hand side marker for header resizing.
113508      * @private
113509      */
113510     getLhsMarker: function() {
113511         var me = this;
113512
113513         if (!me.lhsMarker) {
113514             me.lhsMarker = Ext.DomHelper.append(me.el, {
113515                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113516             }, true);
113517         }
113518         return me.lhsMarker;
113519     },
113520
113521     /**
113522      * Gets right hand side marker for header resizing.
113523      * @private
113524      */
113525     getRhsMarker: function() {
113526         var me = this;
113527
113528         if (!me.rhsMarker) {
113529             me.rhsMarker = Ext.DomHelper.append(me.el, {
113530                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113531             }, true);
113532         }
113533         return me.rhsMarker;
113534     },
113535
113536     /**
113537      * Returns the selection model being used and creates it via the configuration if it has not been created already.
113538      * @return {Ext.selection.Model} selModel
113539      */
113540     getSelectionModel: function(){
113541         if (!this.selModel) {
113542             this.selModel = {};
113543         }
113544
113545         var mode = 'SINGLE',
113546             type;
113547         if (this.simpleSelect) {
113548             mode = 'SIMPLE';
113549         } else if (this.multiSelect) {
113550             mode = 'MULTI';
113551         }
113552
113553         Ext.applyIf(this.selModel, {
113554             allowDeselect: this.allowDeselect,
113555             mode: mode
113556         });
113557
113558         if (!this.selModel.events) {
113559             type = this.selModel.selType || this.selType;
113560             this.selModel = Ext.create('selection.' + type, this.selModel);
113561         }
113562
113563         if (!this.selModel.hasRelaySetup) {
113564             this.relayEvents(this.selModel, [
113565                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
113566             ]);
113567             this.selModel.hasRelaySetup = true;
113568         }
113569
113570         // lock the selection model if user
113571         // has disabled selection
113572         if (this.disableSelection) {
113573             this.selModel.locked = true;
113574         }
113575         return this.selModel;
113576     },
113577
113578     onVerticalScroll: function(event, target) {
113579         var owner = this.getScrollerOwner(),
113580             items = owner.query('tableview'),
113581             i = 0,
113582             len = items.length;
113583
113584         for (; i < len; i++) {
113585             items[i].el.dom.scrollTop = target.scrollTop;
113586         }
113587     },
113588
113589     onHorizontalScroll: function(event, target) {
113590         var owner = this.getScrollerOwner(),
113591             items = owner.query('tableview'),
113592             center = items[1] || items[0];
113593
113594         center.el.dom.scrollLeft = target.scrollLeft;
113595         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
113596     },
113597
113598     // template method meant to be overriden
113599     onStoreLoad: Ext.emptyFn,
113600
113601     getEditorParent: function() {
113602         return this.body;
113603     },
113604
113605     bindStore: function(store) {
113606         var me = this;
113607         me.store = store;
113608         me.getView().bindStore(store);
113609     },
113610     
113611     beforeDestroy: function(){
113612         // may be some duplication here since the horizontal and vertical
113613         // scroller may be part of the docked items, but we need to clean
113614         // them up in case they aren't visible.
113615         Ext.destroy(this.horizontalScroller, this.verticalScroller);
113616         this.callParent();
113617     },
113618
113619     /**
113620      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
113621      * to change them.
113622      * @param {Ext.data.Store} store (Optional) The new store.
113623      * @param {Object[]} columns (Optional) An array of column configs
113624      */
113625     reconfigure: function(store, columns) {
113626         var me = this,
113627             headerCt = me.headerCt;
113628
113629         if (me.lockable) {
113630             me.reconfigureLockable(store, columns);
113631         } else {
113632             if (columns) {
113633                 headerCt.suspendLayout = true;
113634                 headerCt.removeAll();
113635                 headerCt.add(columns);
113636             }
113637             if (store) {
113638                 store = Ext.StoreManager.lookup(store);
113639                 me.bindStore(store);
113640             } else {
113641                 me.getView().refresh();
113642             }
113643             if (columns) {
113644                 headerCt.suspendLayout = false;
113645                 me.forceComponentLayout();
113646             }
113647         }
113648         me.fireEvent('reconfigure', me);
113649     }
113650 });
113651 /**
113652  * This class encapsulates the user interface for a tabular data set.
113653  * It acts as a centralized manager for controlling the various interface
113654  * elements of the view. This includes handling events, such as row and cell
113655  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
113656  * to provide visual feedback to the user.
113657  *
113658  * This class does not provide ways to manipulate the underlying data of the configured
113659  * {@link Ext.data.Store}.
113660  *
113661  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
113662  * to be used directly.
113663  */
113664 Ext.define('Ext.view.Table', {
113665     extend: 'Ext.view.View',
113666     alias: 'widget.tableview',
113667     uses: [
113668         'Ext.view.TableChunker',
113669         'Ext.util.DelayedTask',
113670         'Ext.util.MixedCollection'
113671     ],
113672
113673     baseCls: Ext.baseCSSPrefix + 'grid-view',
113674
113675     // row
113676     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
113677     // cell
113678     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
113679
113680     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
113681     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
113682     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
113683     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
113684     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
113685     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
113686     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
113687
113688     // cfg docs inherited
113689     trackOver: true,
113690
113691     /**
113692      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
113693      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
113694      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
113695      * Example usage:
113696      *
113697      *     viewConfig: {
113698      *         getRowClass: function(record, rowIndex, rowParams, store){
113699      *             return record.get("valid") ? "row-valid" : "row-error";
113700      *         }
113701      *     }
113702      *
113703      * @param {Ext.data.Model} record The record corresponding to the current row.
113704      * @param {Number} index The row index.
113705      * @param {Object} rowParams **DEPRECATED.** For row body use the
113706      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
113707      * @param {Ext.data.Store} store The store this grid is bound to
113708      * @return {String} a CSS class name to add to the row.
113709      * @method
113710      */
113711     getRowClass: null,
113712
113713     initComponent: function() {
113714         var me = this;
113715
113716         me.scrollState = {};
113717         me.selModel.view = me;
113718         me.headerCt.view = me;
113719         me.initFeatures();
113720         me.tpl = '<div></div>';
113721         me.callParent();
113722         me.mon(me.store, {
113723             load: me.onStoreLoad,
113724             scope: me
113725         });
113726
113727         // this.addEvents(
113728         //     /**
113729         //      * @event rowfocus
113730         //      * @param {Ext.data.Model} record
113731         //      * @param {HTMLElement} row
113732         //      * @param {Number} rowIdx
113733         //      */
113734         //     'rowfocus'
113735         // );
113736     },
113737
113738     // scroll to top of the grid when store loads
113739     onStoreLoad: function(){
113740         var me = this;
113741
113742         if (me.invalidateScrollerOnRefresh) {
113743             if (Ext.isGecko) {
113744                 if (!me.scrollToTopTask) {
113745                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
113746                 }
113747                 me.scrollToTopTask.delay(1);
113748             } else {
113749                 me    .scrollToTop();
113750             }
113751         }
113752     },
113753
113754     // scroll the view to the top
113755     scrollToTop: Ext.emptyFn,
113756
113757     /**
113758      * Add a listener to the main view element. It will be destroyed with the view.
113759      * @private
113760      */
113761     addElListener: function(eventName, fn, scope){
113762         this.mon(this, eventName, fn, scope, {
113763             element: 'el'
113764         });
113765     },
113766
113767     /**
113768      * Get the columns used for generating a template via TableChunker.
113769      * See {@link Ext.grid.header.Container#getGridColumns}.
113770      * @private
113771      */
113772     getGridColumns: function() {
113773         return this.headerCt.getGridColumns();
113774     },
113775
113776     /**
113777      * Get a leaf level header by index regardless of what the nesting
113778      * structure is.
113779      * @private
113780      * @param {Number} index The index
113781      */
113782     getHeaderAtIndex: function(index) {
113783         return this.headerCt.getHeaderAtIndex(index);
113784     },
113785
113786     /**
113787      * Get the cell (td) for a particular record and column.
113788      * @param {Ext.data.Model} record
113789      * @param {Ext.grid.column.Column} column
113790      * @private
113791      */
113792     getCell: function(record, column) {
113793         var row = this.getNode(record);
113794         return Ext.fly(row).down(column.getCellSelector());
113795     },
113796
113797     /**
113798      * Get a reference to a feature
113799      * @param {String} id The id of the feature
113800      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
113801      */
113802     getFeature: function(id) {
113803         var features = this.featuresMC;
113804         if (features) {
113805             return features.get(id);
113806         }
113807     },
113808
113809     /**
113810      * Initializes each feature and bind it to this view.
113811      * @private
113812      */
113813     initFeatures: function() {
113814         var me = this,
113815             i = 0,
113816             features,
113817             len;
113818
113819         me.features = me.features || [];
113820         features = me.features;
113821         len = features.length;
113822
113823         me.featuresMC = Ext.create('Ext.util.MixedCollection');
113824         for (; i < len; i++) {
113825             // ensure feature hasnt already been instantiated
113826             if (!features[i].isFeature) {
113827                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
113828             }
113829             // inject a reference to view
113830             features[i].view = me;
113831             me.featuresMC.add(features[i]);
113832         }
113833     },
113834
113835     /**
113836      * Gives features an injection point to attach events to the markup that
113837      * has been created for this view.
113838      * @private
113839      */
113840     attachEventsForFeatures: function() {
113841         var features = this.features,
113842             ln       = features.length,
113843             i        = 0;
113844
113845         for (; i < ln; i++) {
113846             if (features[i].isFeature) {
113847                 features[i].attachEvents();
113848             }
113849         }
113850     },
113851
113852     afterRender: function() {
113853         var me = this;
113854
113855         me.callParent();
113856         me.mon(me.el, {
113857             scroll: me.fireBodyScroll,
113858             scope: me
113859         });
113860         me.el.unselectable();
113861         me.attachEventsForFeatures();
113862     },
113863
113864     fireBodyScroll: function(e, t) {
113865         this.fireEvent('bodyscroll', e, t);
113866     },
113867
113868     // TODO: Refactor headerCt dependency here to colModel
113869     /**
113870      * Uses the headerCt to transform data from dataIndex keys in a record to
113871      * headerId keys in each header and then run them through each feature to
113872      * get additional data for variables they have injected into the view template.
113873      * @private
113874      */
113875     prepareData: function(data, idx, record) {
113876         var me       = this,
113877             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
113878             features = me.features,
113879             ln       = features.length,
113880             i        = 0,
113881             node, feature;
113882
113883         for (; i < ln; i++) {
113884             feature = features[i];
113885             if (feature.isFeature) {
113886                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
113887             }
113888         }
113889
113890         return orig;
113891     },
113892
113893     // TODO: Refactor headerCt dependency here to colModel
113894     collectData: function(records, startIndex) {
113895         var preppedRecords = this.callParent(arguments),
113896             headerCt  = this.headerCt,
113897             fullWidth = headerCt.getFullWidth(),
113898             features  = this.features,
113899             ln = features.length,
113900             o = {
113901                 rows: preppedRecords,
113902                 fullWidth: fullWidth
113903             },
113904             i  = 0,
113905             feature,
113906             j = 0,
113907             jln,
113908             rowParams;
113909
113910         jln = preppedRecords.length;
113911         // process row classes, rowParams has been deprecated and has been moved
113912         // to the individual features that implement the behavior.
113913         if (this.getRowClass) {
113914             for (; j < jln; j++) {
113915                 rowParams = {};
113916                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
113917             }
113918         }
113919         // currently only one feature may implement collectData. This is to modify
113920         // what's returned to the view before its rendered
113921         for (; i < ln; i++) {
113922             feature = features[i];
113923             if (feature.isFeature && feature.collectData && !feature.disabled) {
113924                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
113925                 break;
113926             }
113927         }
113928         return o;
113929     },
113930
113931     // TODO: Refactor header resizing to column resizing
113932     /**
113933      * When a header is resized, setWidth on the individual columns resizer class,
113934      * the top level table, save/restore scroll state, generate a new template and
113935      * restore focus to the grid view's element so that keyboard navigation
113936      * continues to work.
113937      * @private
113938      */
113939     onHeaderResize: function(header, w, suppressFocus) {
113940         var me = this,
113941             el = me.el;
113942
113943         if (el) {
113944             me.saveScrollState();
113945             // Grab the col and set the width, css
113946             // class is generated in TableChunker.
113947             // Select composites because there may be several chunks.
113948
113949             // IE6 and IE7 bug.
113950             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
113951             // We need to increment the passed with in this case.
113952             if (Ext.isIE6 || Ext.isIE7) {
113953                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
113954                     w += 1;
113955                 }
113956             }
113957             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
113958             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
113959             me.restoreScrollState();
113960             if (!me.ignoreTemplate) {
113961                 me.setNewTemplate();
113962             }
113963             if (!suppressFocus) {
113964                 me.el.focus();
113965             }
113966         }
113967     },
113968
113969     /**
113970      * When a header is shown restore its oldWidth if it was previously hidden.
113971      * @private
113972      */
113973     onHeaderShow: function(headerCt, header, suppressFocus) {
113974         var me = this;
113975         me.ignoreTemplate = true;
113976         // restore headers that were dynamically hidden
113977         if (header.oldWidth) {
113978             me.onHeaderResize(header, header.oldWidth, suppressFocus);
113979             delete header.oldWidth;
113980         // flexed headers will have a calculated size set
113981         // this additional check has to do with the fact that
113982         // defaults: {width: 100} will fight with a flex value
113983         } else if (header.width && !header.flex) {
113984             me.onHeaderResize(header, header.width, suppressFocus);
113985         }
113986         delete me.ignoreTemplate;
113987         me.setNewTemplate();
113988     },
113989
113990     /**
113991      * When the header hides treat it as a resize to 0.
113992      * @private
113993      */
113994     onHeaderHide: function(headerCt, header, suppressFocus) {
113995         this.onHeaderResize(header, 0, suppressFocus);
113996     },
113997
113998     /**
113999      * Set a new template based on the current columns displayed in the
114000      * grid.
114001      * @private
114002      */
114003     setNewTemplate: function() {
114004         var me = this,
114005             columns = me.headerCt.getColumnsForTpl(true);
114006
114007         me.tpl = me.getTableChunker().getTableTpl({
114008             columns: columns,
114009             features: me.features
114010         });
114011     },
114012
114013     /**
114014      * Returns the configured chunker or default of Ext.view.TableChunker
114015      */
114016     getTableChunker: function() {
114017         return this.chunker || Ext.view.TableChunker;
114018     },
114019
114020     /**
114021      * Adds a CSS Class to a specific row.
114022      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114023      * representing this row
114024      * @param {String} cls
114025      */
114026     addRowCls: function(rowInfo, cls) {
114027         var row = this.getNode(rowInfo);
114028         if (row) {
114029             Ext.fly(row).addCls(cls);
114030         }
114031     },
114032
114033     /**
114034      * Removes a CSS Class from a specific row.
114035      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114036      * representing this row
114037      * @param {String} cls
114038      */
114039     removeRowCls: function(rowInfo, cls) {
114040         var row = this.getNode(rowInfo);
114041         if (row) {
114042             Ext.fly(row).removeCls(cls);
114043         }
114044     },
114045
114046     // GridSelectionModel invokes onRowSelect as selection changes
114047     onRowSelect : function(rowIdx) {
114048         this.addRowCls(rowIdx, this.selectedItemCls);
114049     },
114050
114051     // GridSelectionModel invokes onRowDeselect as selection changes
114052     onRowDeselect : function(rowIdx) {
114053         var me = this;
114054
114055         me.removeRowCls(rowIdx, me.selectedItemCls);
114056         me.removeRowCls(rowIdx, me.focusedItemCls);
114057     },
114058
114059     onCellSelect: function(position) {
114060         var cell = this.getCellByPosition(position);
114061         if (cell) {
114062             cell.addCls(this.selectedCellCls);
114063         }
114064     },
114065
114066     onCellDeselect: function(position) {
114067         var cell = this.getCellByPosition(position);
114068         if (cell) {
114069             cell.removeCls(this.selectedCellCls);
114070         }
114071
114072     },
114073
114074     onCellFocus: function(position) {
114075         //var cell = this.getCellByPosition(position);
114076         this.focusCell(position);
114077     },
114078
114079     getCellByPosition: function(position) {
114080         var row    = position.row,
114081             column = position.column,
114082             store  = this.store,
114083             node   = this.getNode(row),
114084             header = this.headerCt.getHeaderAtIndex(column),
114085             cellSelector,
114086             cell = false;
114087
114088         if (header && node) {
114089             cellSelector = header.getCellSelector();
114090             cell = Ext.fly(node).down(cellSelector);
114091         }
114092         return cell;
114093     },
114094
114095     // GridSelectionModel invokes onRowFocus to 'highlight'
114096     // the last row focused
114097     onRowFocus: function(rowIdx, highlight, supressFocus) {
114098         var me = this,
114099             row = me.getNode(rowIdx);
114100
114101         if (highlight) {
114102             me.addRowCls(rowIdx, me.focusedItemCls);
114103             if (!supressFocus) {
114104                 me.focusRow(rowIdx);
114105             }
114106             //this.el.dom.setAttribute('aria-activedescendant', row.id);
114107         } else {
114108             me.removeRowCls(rowIdx, me.focusedItemCls);
114109         }
114110     },
114111
114112     /**
114113      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
114114      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
114115      * An HTMLElement template node, index of a template node, the id of a template node or the
114116      * record associated with the node.
114117      */
114118     focusRow: function(rowIdx) {
114119         var me         = this,
114120             row        = me.getNode(rowIdx),
114121             el         = me.el,
114122             adjustment = 0,
114123             panel      = me.ownerCt,
114124             rowRegion,
114125             elRegion,
114126             record;
114127
114128         if (row && el) {
114129             elRegion  = el.getRegion();
114130             rowRegion = Ext.fly(row).getRegion();
114131             // row is above
114132             if (rowRegion.top < elRegion.top) {
114133                 adjustment = rowRegion.top - elRegion.top;
114134             // row is below
114135             } else if (rowRegion.bottom > elRegion.bottom) {
114136                 adjustment = rowRegion.bottom - elRegion.bottom;
114137             }
114138             record = me.getRecord(row);
114139             rowIdx = me.store.indexOf(record);
114140
114141             if (adjustment) {
114142                 // scroll the grid itself, so that all gridview's update.
114143                 panel.scrollByDeltaY(adjustment);
114144             }
114145             me.fireEvent('rowfocus', record, row, rowIdx);
114146         }
114147     },
114148
114149     focusCell: function(position) {
114150         var me          = this,
114151             cell        = me.getCellByPosition(position),
114152             el          = me.el,
114153             adjustmentY = 0,
114154             adjustmentX = 0,
114155             elRegion    = el.getRegion(),
114156             panel       = me.ownerCt,
114157             cellRegion,
114158             record;
114159
114160         if (cell) {
114161             cellRegion = cell.getRegion();
114162             // cell is above
114163             if (cellRegion.top < elRegion.top) {
114164                 adjustmentY = cellRegion.top - elRegion.top;
114165             // cell is below
114166             } else if (cellRegion.bottom > elRegion.bottom) {
114167                 adjustmentY = cellRegion.bottom - elRegion.bottom;
114168             }
114169
114170             // cell is left
114171             if (cellRegion.left < elRegion.left) {
114172                 adjustmentX = cellRegion.left - elRegion.left;
114173             // cell is right
114174             } else if (cellRegion.right > elRegion.right) {
114175                 adjustmentX = cellRegion.right - elRegion.right;
114176             }
114177
114178             if (adjustmentY) {
114179                 // scroll the grid itself, so that all gridview's update.
114180                 panel.scrollByDeltaY(adjustmentY);
114181             }
114182             if (adjustmentX) {
114183                 panel.scrollByDeltaX(adjustmentX);
114184             }
114185             el.focus();
114186             me.fireEvent('cellfocus', record, cell, position);
114187         }
114188     },
114189
114190     /**
114191      * Scrolls by delta. This affects this individual view ONLY and does not
114192      * synchronize across views or scrollers.
114193      * @param {Number} delta
114194      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
114195      * @private
114196      */
114197     scrollByDelta: function(delta, dir) {
114198         dir = dir || 'scrollTop';
114199         var elDom = this.el.dom;
114200         elDom[dir] = (elDom[dir] += delta);
114201     },
114202
114203     onUpdate: function(ds, index) {
114204         this.callParent(arguments);
114205     },
114206
114207     /**
114208      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
114209      */
114210     saveScrollState: function() {
114211         if (this.rendered) {
114212             var dom = this.el.dom, 
114213                 state = this.scrollState;
114214             
114215             state.left = dom.scrollLeft;
114216             state.top = dom.scrollTop;
114217         }
114218     },
114219
114220     /**
114221      * Restores the scrollState.
114222      * Must be used in conjunction with saveScrollState
114223      * @private
114224      */
114225     restoreScrollState: function() {
114226         if (this.rendered) {
114227             var dom = this.el.dom, 
114228                 state = this.scrollState, 
114229                 headerEl = this.headerCt.el.dom;
114230             
114231             headerEl.scrollLeft = dom.scrollLeft = state.left;
114232             dom.scrollTop = state.top;
114233         }
114234     },
114235
114236     /**
114237      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
114238      * invalidates the scrollers.
114239      */
114240     refresh: function() {
114241         this.setNewTemplate();
114242         this.callParent(arguments);
114243     },
114244
114245     processItemEvent: function(record, row, rowIndex, e) {
114246         var me = this,
114247             cell = e.getTarget(me.cellSelector, row),
114248             cellIndex = cell ? cell.cellIndex : -1,
114249             map = me.statics().EventMap,
114250             selModel = me.getSelectionModel(),
114251             type = e.type,
114252             result;
114253
114254         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
114255             // CellModel, otherwise we can't tell which cell to invoke
114256             cell = me.getCellByPosition(selModel.getCurrentPosition());
114257             if (cell) {
114258                 cell = cell.dom;
114259                 cellIndex = cell.cellIndex;
114260             }
114261         }
114262
114263         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
114264
114265         if (result === false || me.callParent(arguments) === false) {
114266             return false;
114267         }
114268
114269         // Don't handle cellmouseenter and cellmouseleave events for now
114270         if (type == 'mouseover' || type == 'mouseout') {
114271             return true;
114272         }
114273
114274         return !(
114275             // We are adding cell and feature events
114276             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114277             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
114278             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114279             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
114280         );
114281     },
114282
114283     processSpecialEvent: function(e) {
114284         var me = this,
114285             map = me.statics().EventMap,
114286             features = me.features,
114287             ln = features.length,
114288             type = e.type,
114289             i, feature, prefix, featureTarget,
114290             beforeArgs, args,
114291             panel = me.ownerCt;
114292
114293         me.callParent(arguments);
114294
114295         if (type == 'mouseover' || type == 'mouseout') {
114296             return;
114297         }
114298
114299         for (i = 0; i < ln; i++) {
114300             feature = features[i];
114301             if (feature.hasFeatureEvent) {
114302                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
114303                 if (featureTarget) {
114304                     prefix = feature.eventPrefix;
114305                     // allows features to implement getFireEventArgs to change the
114306                     // fireEvent signature
114307                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
114308                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
114309
114310                     if (
114311                         // before view event
114312                         (me.fireEvent.apply(me, beforeArgs) === false) ||
114313                         // panel grid event
114314                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
114315                         // view event
114316                         (me.fireEvent.apply(me, args) === false) ||
114317                         // panel event
114318                         (panel.fireEvent.apply(panel, args) === false)
114319                     ) {
114320                         return false;
114321                     }
114322                 }
114323             }
114324         }
114325         return true;
114326     },
114327
114328     onCellMouseDown: Ext.emptyFn,
114329     onCellMouseUp: Ext.emptyFn,
114330     onCellClick: Ext.emptyFn,
114331     onCellDblClick: Ext.emptyFn,
114332     onCellContextMenu: Ext.emptyFn,
114333     onCellKeyDown: Ext.emptyFn,
114334     onBeforeCellMouseDown: Ext.emptyFn,
114335     onBeforeCellMouseUp: Ext.emptyFn,
114336     onBeforeCellClick: Ext.emptyFn,
114337     onBeforeCellDblClick: Ext.emptyFn,
114338     onBeforeCellContextMenu: Ext.emptyFn,
114339     onBeforeCellKeyDown: Ext.emptyFn,
114340
114341     /**
114342      * Expands a particular header to fit the max content width.
114343      * This will ONLY expand, not contract.
114344      * @private
114345      */
114346     expandToFit: function(header) {
114347         if (header) {
114348             var maxWidth = this.getMaxContentWidth(header);
114349             delete header.flex;
114350             header.setWidth(maxWidth);
114351         }
114352     },
114353
114354     /**
114355      * Returns the max contentWidth of the header's text and all cells
114356      * in the grid under this header.
114357      * @private
114358      */
114359     getMaxContentWidth: function(header) {
114360         var cellSelector = header.getCellInnerSelector(),
114361             cells        = this.el.query(cellSelector),
114362             i = 0,
114363             ln = cells.length,
114364             maxWidth = header.el.dom.scrollWidth,
114365             scrollWidth;
114366
114367         for (; i < ln; i++) {
114368             scrollWidth = cells[i].scrollWidth;
114369             if (scrollWidth > maxWidth) {
114370                 maxWidth = scrollWidth;
114371             }
114372         }
114373         return maxWidth;
114374     },
114375
114376     getPositionByEvent: function(e) {
114377         var me       = this,
114378             cellNode = e.getTarget(me.cellSelector),
114379             rowNode  = e.getTarget(me.itemSelector),
114380             record   = me.getRecord(rowNode),
114381             header   = me.getHeaderByCell(cellNode);
114382
114383         return me.getPosition(record, header);
114384     },
114385
114386     getHeaderByCell: function(cell) {
114387         if (cell) {
114388             var m = cell.className.match(this.cellRe);
114389             if (m && m[1]) {
114390                 return Ext.getCmp(m[1]);
114391             }
114392         }
114393         return false;
114394     },
114395
114396     /**
114397      * @param {Object} position The current row and column: an object containing the following properties:
114398      *
114399      * - row - The row index
114400      * - column - The column index
114401      *
114402      * @param {String} direction 'up', 'down', 'right' and 'left'
114403      * @param {Ext.EventObject} e event
114404      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
114405      * @param {Function} verifierFn A function to verify the validity of the calculated position.
114406      * When using this function, you must return true to allow the newPosition to be returned.
114407      * @param {Object} scope Scope to run the verifierFn in
114408      * @returns {Object} newPosition An object containing the following properties:
114409      *
114410      * - row - The row index
114411      * - column - The column index
114412      *
114413      * @private
114414      */
114415     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
114416         var me       = this,
114417             row      = pos.row,
114418             column   = pos.column,
114419             rowCount = me.store.getCount(),
114420             firstCol = me.getFirstVisibleColumnIndex(),
114421             lastCol  = me.getLastVisibleColumnIndex(),
114422             newPos   = {row: row, column: column},
114423             activeHeader = me.headerCt.getHeaderAtIndex(column);
114424
114425         // no active header or its currently hidden
114426         if (!activeHeader || activeHeader.hidden) {
114427             return false;
114428         }
114429
114430         e = e || {};
114431         direction = direction.toLowerCase();
114432         switch (direction) {
114433             case 'right':
114434                 // has the potential to wrap if its last
114435                 if (column === lastCol) {
114436                     // if bottom row and last column, deny right
114437                     if (preventWrap || row === rowCount - 1) {
114438                         return false;
114439                     }
114440                     if (!e.ctrlKey) {
114441                         // otherwise wrap to nextRow and firstCol
114442                         newPos.row = row + 1;
114443                         newPos.column = firstCol;
114444                     }
114445                 // go right
114446                 } else {
114447                     if (!e.ctrlKey) {
114448                         newPos.column = column + me.getRightGap(activeHeader);
114449                     } else {
114450                         newPos.column = lastCol;
114451                     }
114452                 }
114453                 break;
114454
114455             case 'left':
114456                 // has the potential to wrap
114457                 if (column === firstCol) {
114458                     // if top row and first column, deny left
114459                     if (preventWrap || row === 0) {
114460                         return false;
114461                     }
114462                     if (!e.ctrlKey) {
114463                         // otherwise wrap to prevRow and lastCol
114464                         newPos.row = row - 1;
114465                         newPos.column = lastCol;
114466                     }
114467                 // go left
114468                 } else {
114469                     if (!e.ctrlKey) {
114470                         newPos.column = column + me.getLeftGap(activeHeader);
114471                     } else {
114472                         newPos.column = firstCol;
114473                     }
114474                 }
114475                 break;
114476
114477             case 'up':
114478                 // if top row, deny up
114479                 if (row === 0) {
114480                     return false;
114481                 // go up
114482                 } else {
114483                     if (!e.ctrlKey) {
114484                         newPos.row = row - 1;
114485                     } else {
114486                         newPos.row = 0;
114487                     }
114488                 }
114489                 break;
114490
114491             case 'down':
114492                 // if bottom row, deny down
114493                 if (row === rowCount - 1) {
114494                     return false;
114495                 // go down
114496                 } else {
114497                     if (!e.ctrlKey) {
114498                         newPos.row = row + 1;
114499                     } else {
114500                         newPos.row = rowCount - 1;
114501                     }
114502                 }
114503                 break;
114504         }
114505
114506         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
114507             return false;
114508         } else {
114509             return newPos;
114510         }
114511     },
114512     getFirstVisibleColumnIndex: function() {
114513         var headerCt   = this.getHeaderCt(),
114514             allColumns = headerCt.getGridColumns(),
114515             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114516             firstHeader = visHeaders[0];
114517
114518         return headerCt.getHeaderIndex(firstHeader);
114519     },
114520
114521     getLastVisibleColumnIndex: function() {
114522         var headerCt   = this.getHeaderCt(),
114523             allColumns = headerCt.getGridColumns(),
114524             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114525             lastHeader = visHeaders[visHeaders.length - 1];
114526
114527         return headerCt.getHeaderIndex(lastHeader);
114528     },
114529
114530     getHeaderCt: function() {
114531         return this.headerCt;
114532     },
114533
114534     getPosition: function(record, header) {
114535         var me = this,
114536             store = me.store,
114537             gridCols = me.headerCt.getGridColumns();
114538
114539         return {
114540             row: store.indexOf(record),
114541             column: Ext.Array.indexOf(gridCols, header)
114542         };
114543     },
114544
114545     /**
114546      * Determines the 'gap' between the closest adjacent header to the right
114547      * that is not hidden.
114548      * @private
114549      */
114550     getRightGap: function(activeHeader) {
114551         var headerCt        = this.getHeaderCt(),
114552             headers         = headerCt.getGridColumns(),
114553             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114554             i               = activeHeaderIdx + 1,
114555             nextIdx;
114556
114557         for (; i <= headers.length; i++) {
114558             if (!headers[i].hidden) {
114559                 nextIdx = i;
114560                 break;
114561             }
114562         }
114563
114564         return nextIdx - activeHeaderIdx;
114565     },
114566
114567     beforeDestroy: function() {
114568         if (this.rendered) {
114569             this.el.removeAllListeners();
114570         }
114571         this.callParent(arguments);
114572     },
114573
114574     /**
114575      * Determines the 'gap' between the closest adjacent header to the left
114576      * that is not hidden.
114577      * @private
114578      */
114579     getLeftGap: function(activeHeader) {
114580         var headerCt        = this.getHeaderCt(),
114581             headers         = headerCt.getGridColumns(),
114582             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114583             i               = activeHeaderIdx - 1,
114584             prevIdx;
114585
114586         for (; i >= 0; i--) {
114587             if (!headers[i].hidden) {
114588                 prevIdx = i;
114589                 break;
114590             }
114591         }
114592
114593         return prevIdx - activeHeaderIdx;
114594     }
114595 });
114596 /**
114597  * @class Ext.grid.View
114598  * @extends Ext.view.Table
114599  *
114600  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
114601  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
114602  * option is passed to the grid:
114603  *
114604  *     Ext.create('Ext.grid.Panel', {
114605  *         // other options
114606  *         viewConfig: {
114607  *             stripeRows: false
114608  *         }
114609  *     });
114610  *
114611  * ## Drag Drop
114612  *
114613  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
114614  * when creating the view.
114615  *
114616  *     Ext.create('Ext.grid.Panel', {
114617  *         // other options
114618  *         viewConfig: {
114619  *             plugins: {
114620  *                 ddGroup: 'people-group',
114621  *                 ptype: 'gridviewdragdrop',
114622  *                 enableDrop: false
114623  *             }
114624  *         }
114625  *     });
114626  */
114627 Ext.define('Ext.grid.View', {
114628     extend: 'Ext.view.Table',
114629     alias: 'widget.gridview',
114630
114631     /**
114632      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
114633      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
114634      * the grid. A default CSS rule is provided which sets a background color, but you can override this
114635      * with a rule which either overrides the <b>background-color</b> style using the '!important'
114636      * modifier, or which uses a CSS selector of higher specificity.</p>
114637      */
114638     stripeRows: true,
114639
114640     invalidateScrollerOnRefresh: true,
114641
114642     /**
114643      * Scroll the GridView to the top by scrolling the scroller.
114644      * @private
114645      */
114646     scrollToTop : function(){
114647         if (this.rendered) {
114648             var section = this.ownerCt,
114649                 verticalScroller = section.verticalScroller;
114650
114651             if (verticalScroller) {
114652                 verticalScroller.scrollToTop();
114653             }
114654         }
114655     },
114656
114657     // after adding a row stripe rows from then on
114658     onAdd: function(ds, records, index) {
114659         this.callParent(arguments);
114660         this.doStripeRows(index);
114661     },
114662
114663     // after removing a row stripe rows from then on
114664     onRemove: function(ds, records, index) {
114665         this.callParent(arguments);
114666         this.doStripeRows(index);
114667     },
114668
114669     onUpdate: function(ds, record, operation) {
114670         var index = ds.indexOf(record);
114671         this.callParent(arguments);
114672         this.doStripeRows(index, index);
114673     },
114674
114675     /**
114676      * Stripe rows from a particular row index
114677      * @param {Number} startRow
114678      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
114679      * @private
114680      */
114681     doStripeRows: function(startRow, endRow) {
114682         // ensure stripeRows configuration is turned on
114683         if (this.stripeRows) {
114684             var rows   = this.getNodes(startRow, endRow),
114685                 rowsLn = rows.length,
114686                 i      = 0,
114687                 row;
114688
114689             for (; i < rowsLn; i++) {
114690                 row = rows[i];
114691                 // Remove prior applied row classes.
114692                 row.className = row.className.replace(this.rowClsRe, ' ');
114693                 startRow++;
114694                 // Every odd row will get an additional cls
114695                 if (startRow % 2 === 0) {
114696                     row.className += (' ' + this.altRowCls);
114697                 }
114698             }
114699         }
114700     },
114701
114702     refresh: function(firstPass) {
114703         this.callParent(arguments);
114704         this.doStripeRows(0);
114705         // TODO: Remove gridpanel dependency
114706         var g = this.up('gridpanel');
114707         if (g && this.invalidateScrollerOnRefresh) {
114708             g.invalidateScroller();
114709         }
114710     }
114711 });
114712
114713 /**
114714  * @author Aaron Conran
114715  * @docauthor Ed Spencer
114716  *
114717  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
114718  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
114719  *
114720  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
114721  *
114722  * ## Basic GridPanel
114723  *
114724  *     @example
114725  *     Ext.create('Ext.data.Store', {
114726  *         storeId:'simpsonsStore',
114727  *         fields:['name', 'email', 'phone'],
114728  *         data:{'items':[
114729  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
114730  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
114731  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
114732  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
114733  *         ]},
114734  *         proxy: {
114735  *             type: 'memory',
114736  *             reader: {
114737  *                 type: 'json',
114738  *                 root: 'items'
114739  *             }
114740  *         }
114741  *     });
114742  *
114743  *     Ext.create('Ext.grid.Panel', {
114744  *         title: 'Simpsons',
114745  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114746  *         columns: [
114747  *             { header: 'Name',  dataIndex: 'name' },
114748  *             { header: 'Email', dataIndex: 'email', flex: 1 },
114749  *             { header: 'Phone', dataIndex: 'phone' }
114750  *         ],
114751  *         height: 200,
114752  *         width: 400,
114753  *         renderTo: Ext.getBody()
114754  *     });
114755  *
114756  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
114757  * In most apps we would be placing the grid inside another container and wouldn't need to use the
114758  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
114759  * up and running.
114760  *
114761  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
114762  * and finally the grid rows under the headers.
114763  *
114764  * ## Configuring columns
114765  *
114766  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
114767  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
114768  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
114769  *
114770  *     columns: [
114771  *         {
114772  *             header: 'Name',
114773  *             dataIndex: 'name',
114774  *             sortable: false,
114775  *             hideable: false,
114776  *             flex: 1
114777  *         },
114778  *         {
114779  *             header: 'Email',
114780  *             dataIndex: 'email',
114781  *             hidden: true
114782  *         },
114783  *         {
114784  *             header: 'Phone',
114785  *             dataIndex: 'phone',
114786  *             width: 100
114787  *         }
114788  *     ]
114789  *
114790  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
114791  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
114792  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
114793  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
114794  *
114795  * ## Renderers
114796  *
114797  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
114798  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
114799  * we could define a renderer function for the email column to turn each email address into a mailto link:
114800  *
114801  *     columns: [
114802  *         {
114803  *             header: 'Email',
114804  *             dataIndex: 'email',
114805  *             renderer: function(value) {
114806  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
114807  *             }
114808  *         }
114809  *     ]
114810  *
114811  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
114812  *
114813  * ## Selection Models
114814  *
114815  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
114816  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
114817  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
114818  * CellSelectionModel, where individual cells are selected.
114819  *
114820  * Grids use a Row Selection Model by default, but this is easy to customise like so:
114821  *
114822  *     Ext.create('Ext.grid.Panel', {
114823  *         selType: 'cellmodel',
114824  *         store: ...
114825  *     });
114826  *
114827  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
114828  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
114829  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
114830  * conjunction with editing.
114831  *
114832  * ## Editing
114833  *
114834  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
114835  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
114836  * on both the name and the email columns:
114837  *
114838  *     Ext.create('Ext.grid.Panel', {
114839  *         title: 'Simpsons',
114840  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114841  *         columns: [
114842  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114843  *             { header: 'Email', dataIndex: 'email', flex: 1,
114844  *                 field: {
114845  *                     xtype: 'textfield',
114846  *                     allowBlank: false
114847  *                 }
114848  *             },
114849  *             { header: 'Phone', dataIndex: 'phone' }
114850  *         ],
114851  *         selType: 'cellmodel',
114852  *         plugins: [
114853  *             Ext.create('Ext.grid.plugin.CellEditing', {
114854  *                 clicksToEdit: 1
114855  *             })
114856  *         ],
114857  *         height: 200,
114858  *         width: 400,
114859  *         renderTo: Ext.getBody()
114860  *     });
114861  *
114862  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
114863  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
114864  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
114865  * editor slightly by passing allowBlank: false, which will provide inline validation.
114866  *
114867  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
114868  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
114869  * single click.
114870  *
114871  * ## Row Editing
114872  *
114873  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
114874  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
114875  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
114876  *
114877  *     Ext.create('Ext.grid.Panel', {
114878  *         title: 'Simpsons',
114879  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114880  *         columns: [
114881  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114882  *             { header: 'Email', dataIndex: 'email', flex:1,
114883  *                 field: {
114884  *                     xtype: 'textfield',
114885  *                     allowBlank: false
114886  *                 }
114887  *             },
114888  *             { header: 'Phone', dataIndex: 'phone' }
114889  *         ],
114890  *         selType: 'rowmodel',
114891  *         plugins: [
114892  *             Ext.create('Ext.grid.plugin.RowEditing', {
114893  *                 clicksToEdit: 1
114894  *             })
114895  *         ],
114896  *         height: 200,
114897  *         width: 400,
114898  *         renderTo: Ext.getBody()
114899  *     });
114900  *
114901  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
114902  * editor will appear and enable us to edit each of the columns we have specified an editor for.
114903  *
114904  * ## Sorting & Filtering
114905  *
114906  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
114907  * easy to set up a grid to be sorted from the start:
114908  *
114909  *     var myGrid = Ext.create('Ext.grid.Panel', {
114910  *         store: {
114911  *             fields: ['name', 'email', 'phone'],
114912  *             sorters: ['name', 'phone']
114913  *         },
114914  *         columns: [
114915  *             { text: 'Name',  dataIndex: 'name' },
114916  *             { text: 'Email', dataIndex: 'email' }
114917  *         ]
114918  *     });
114919  *
114920  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
114921  * more than one field at run time it's easy to do so by adding new sorters to the store:
114922  *
114923  *     myGrid.store.sort([
114924  *         { property: 'name',  direction: 'ASC' },
114925  *         { property: 'email', direction: 'DESC' }
114926  *     ]);
114927  *
114928  * See {@link Ext.data.Store} for examples of filtering.
114929  *
114930  * ## Grouping
114931  *
114932  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
114933  * group by the department that each employee works in. Here's how we might set that up:
114934  *
114935  *     @example
114936  *     var store = Ext.create('Ext.data.Store', {
114937  *         storeId:'employeeStore',
114938  *         fields:['name', 'senority', 'department'],
114939  *         groupField: 'department',
114940  *         data: {'employees':[
114941  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
114942  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
114943  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
114944  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
114945  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
114946  *         ]},
114947  *         proxy: {
114948  *             type: 'memory',
114949  *             reader: {
114950  *                 type: 'json',
114951  *                 root: 'employees'
114952  *             }
114953  *         }
114954  *     });
114955  *
114956  *     Ext.create('Ext.grid.Panel', {
114957  *         title: 'Employees',
114958  *         store: Ext.data.StoreManager.lookup('employeeStore'),
114959  *         columns: [
114960  *             { header: 'Name',     dataIndex: 'name' },
114961  *             { header: 'Senority', dataIndex: 'senority' }
114962  *         ],
114963  *         features: [{ftype:'grouping'}],
114964  *         width: 200,
114965  *         height: 275,
114966  *         renderTo: Ext.getBody()
114967  *     });
114968  *
114969  * ## Infinite Scrolling
114970  *
114971  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
114972  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
114973  * to a store with a pageSize specified.
114974  *
114975  *     var grid = Ext.create('Ext.grid.Panel', {
114976  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
114977  *         verticalScrollerType: 'paginggridscroller',
114978  *         // do not reset the scrollbar when the view refreshs
114979  *         invalidateScrollerOnRefresh: false,
114980  *         // infinite scrolling does not support selection
114981  *         disableSelection: true,
114982  *         // ...
114983  *     });
114984  *
114985  * ## Paging
114986  *
114987  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
114988  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
114989  *
114990  *     @example
114991  *     var itemsPerPage = 2;   // set the number of items you want per page
114992  *
114993  *     var store = Ext.create('Ext.data.Store', {
114994  *         id:'simpsonsStore',
114995  *         autoLoad: false,
114996  *         fields:['name', 'email', 'phone'],
114997  *         pageSize: itemsPerPage, // items per page
114998  *         proxy: {
114999  *             type: 'ajax',
115000  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
115001  *             reader: {
115002  *                 type: 'json',
115003  *                 root: 'items',
115004  *                 totalProperty: 'total'
115005  *             }
115006  *         }
115007  *     });
115008  *
115009  *     // specify segment of data you want to load using params
115010  *     store.load({
115011  *         params:{
115012  *             start:0,
115013  *             limit: itemsPerPage
115014  *         }
115015  *     });
115016  *
115017  *     Ext.create('Ext.grid.Panel', {
115018  *         title: 'Simpsons',
115019  *         store: store,
115020  *         columns: [
115021  *             {header: 'Name',  dataIndex: 'name'},
115022  *             {header: 'Email', dataIndex: 'email', flex:1},
115023  *             {header: 'Phone', dataIndex: 'phone'}
115024  *         ],
115025  *         width: 400,
115026  *         height: 125,
115027  *         dockedItems: [{
115028  *             xtype: 'pagingtoolbar',
115029  *             store: store,   // same store GridPanel is using
115030  *             dock: 'bottom',
115031  *             displayInfo: true
115032  *         }],
115033  *         renderTo: Ext.getBody()
115034  *     });
115035  */
115036 Ext.define('Ext.grid.Panel', {
115037     extend: 'Ext.panel.Table',
115038     requires: ['Ext.grid.View'],
115039     alias: ['widget.gridpanel', 'widget.grid'],
115040     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
115041     viewType: 'gridview',
115042
115043     lockable: false,
115044
115045     // Required for the Lockable Mixin. These are the configurations which will be copied to the
115046     // normal and locked sub tablepanels
115047     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
115048     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
115049
115050     /**
115051      * @cfg {Boolean} [columnLines=false] Adds column line styling
115052      */
115053
115054     initComponent: function() {
115055         var me = this;
115056
115057         if (me.columnLines) {
115058             me.setColumnLines(me.columnLines);
115059         }
115060
115061         me.callParent();
115062     },
115063
115064     setColumnLines: function(show) {
115065         var me = this,
115066             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
115067
115068         me[method]('with-col-lines');
115069     }
115070 });
115071
115072 // Currently has the following issues:
115073 // - Does not handle postEditValue
115074 // - Fields without editors need to sync with their values in Store
115075 // - starting to edit another record while already editing and dirty should probably prevent it
115076 // - aggregating validation messages
115077 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
115078 // - layout issues when changing sizes/width while hidden (layout bug)
115079
115080 /**
115081  * @class Ext.grid.RowEditor
115082  * @extends Ext.form.Panel
115083  *
115084  * Internal utility class used to provide row editing functionality. For developers, they should use
115085  * the RowEditing plugin to use this functionality with a grid.
115086  *
115087  * @ignore
115088  */
115089 Ext.define('Ext.grid.RowEditor', {
115090     extend: 'Ext.form.Panel',
115091     requires: [
115092         'Ext.tip.ToolTip',
115093         'Ext.util.HashMap',
115094         'Ext.util.KeyNav'
115095     ],
115096
115097     saveBtnText  : 'Update',
115098     cancelBtnText: 'Cancel',
115099     errorsText: 'Errors',
115100     dirtyText: 'You need to commit or cancel your changes',
115101
115102     lastScrollLeft: 0,
115103     lastScrollTop: 0,
115104
115105     border: false,
115106     
115107     // Change the hideMode to offsets so that we get accurate measurements when
115108     // the roweditor is hidden for laying out things like a TriggerField.
115109     hideMode: 'offsets',
115110
115111     initComponent: function() {
115112         var me = this,
115113             form;
115114
115115         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
115116
115117         me.layout = {
115118             type: 'hbox',
115119             align: 'middle'
115120         };
115121
115122         // Maintain field-to-column mapping
115123         // It's easy to get a field from a column, but not vice versa
115124         me.columns = Ext.create('Ext.util.HashMap');
115125         me.columns.getKey = function(columnHeader) {
115126             var f;
115127             if (columnHeader.getEditor) {
115128                 f = columnHeader.getEditor();
115129                 if (f) {
115130                     return f.id;
115131                 }
115132             }
115133             return columnHeader.id;
115134         };
115135         me.mon(me.columns, {
115136             add: me.onFieldAdd,
115137             remove: me.onFieldRemove,
115138             replace: me.onFieldReplace,
115139             scope: me
115140         });
115141
115142         me.callParent(arguments);
115143
115144         if (me.fields) {
115145             me.setField(me.fields);
115146             delete me.fields;
115147         }
115148
115149         form = me.getForm();
115150         form.trackResetOnLoad = true;
115151     },
115152
115153     onFieldChange: function() {
115154         var me = this,
115155             form = me.getForm(),
115156             valid = form.isValid();
115157         if (me.errorSummary && me.isVisible()) {
115158             me[valid ? 'hideToolTip' : 'showToolTip']();
115159         }
115160         if (me.floatingButtons) {
115161             me.floatingButtons.child('#update').setDisabled(!valid);
115162         }
115163         me.isValid = valid;
115164     },
115165
115166     afterRender: function() {
115167         var me = this,
115168             plugin = me.editingPlugin;
115169
115170         me.callParent(arguments);
115171         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
115172
115173         // Prevent from bubbling click events to the grid view
115174         me.mon(me.el, {
115175             click: Ext.emptyFn,
115176             stopPropagation: true
115177         });
115178
115179         me.el.swallowEvent([
115180             'keypress',
115181             'keydown'
115182         ]);
115183
115184         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
115185             enter: plugin.completeEdit,
115186             esc: plugin.onEscKey,
115187             scope: plugin
115188         });
115189
115190         me.mon(plugin.view, {
115191             beforerefresh: me.onBeforeViewRefresh,
115192             refresh: me.onViewRefresh,
115193             scope: me
115194         });
115195     },
115196
115197     onBeforeViewRefresh: function(view) {
115198         var me = this,
115199             viewDom = view.el.dom;
115200
115201         if (me.el.dom.parentNode === viewDom) {
115202             viewDom.removeChild(me.el.dom);
115203         }
115204     },
115205
115206     onViewRefresh: function(view) {
115207         var me = this,
115208             viewDom = view.el.dom,
115209             context = me.context,
115210             idx;
115211
115212         viewDom.appendChild(me.el.dom);
115213
115214         // Recover our row node after a view refresh
115215         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
115216             context.row = view.getNode(idx);
115217             me.reposition();
115218             if (me.tooltip && me.tooltip.isVisible()) {
115219                 me.tooltip.setTarget(context.row);
115220             }
115221         } else {
115222             me.editingPlugin.cancelEdit();
115223         }
115224     },
115225
115226     onCtScroll: function(e, target) {
115227         var me = this,
115228             scrollTop  = target.scrollTop,
115229             scrollLeft = target.scrollLeft;
115230
115231         if (scrollTop !== me.lastScrollTop) {
115232             me.lastScrollTop = scrollTop;
115233             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
115234                 me.repositionTip();
115235             }
115236         }
115237         if (scrollLeft !== me.lastScrollLeft) {
115238             me.lastScrollLeft = scrollLeft;
115239             me.reposition();
115240         }
115241     },
115242
115243     onColumnAdd: function(column) {
115244         this.setField(column);
115245     },
115246
115247     onColumnRemove: function(column) {
115248         this.columns.remove(column);
115249     },
115250
115251     onColumnResize: function(column, width) {
115252         column.getEditor().setWidth(width - 2);
115253         if (this.isVisible()) {
115254             this.reposition();
115255         }
115256     },
115257
115258     onColumnHide: function(column) {
115259         column.getEditor().hide();
115260         if (this.isVisible()) {
115261             this.reposition();
115262         }
115263     },
115264
115265     onColumnShow: function(column) {
115266         var field = column.getEditor();
115267         field.setWidth(column.getWidth() - 2).show();
115268         if (this.isVisible()) {
115269             this.reposition();
115270         }
115271     },
115272
115273     onColumnMove: function(column, fromIdx, toIdx) {
115274         var field = column.getEditor();
115275         if (this.items.indexOf(field) != toIdx) {
115276             this.move(fromIdx, toIdx);
115277         }
115278     },
115279
115280     onFieldAdd: function(map, fieldId, column) {
115281         var me = this,
115282             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
115283             field = column.getEditor({ xtype: 'displayfield' });
115284
115285         me.insert(colIdx, field);
115286     },
115287
115288     onFieldRemove: function(map, fieldId, column) {
115289         var me = this,
115290             field = column.getEditor(),
115291             fieldEl = field.el;
115292         me.remove(field, false);
115293         if (fieldEl) {
115294             fieldEl.remove();
115295         }
115296     },
115297
115298     onFieldReplace: function(map, fieldId, column, oldColumn) {
115299         var me = this;
115300         me.onFieldRemove(map, fieldId, oldColumn);
115301     },
115302
115303     clearFields: function() {
115304         var me = this,
115305             map = me.columns;
115306         map.each(function(fieldId) {
115307             map.removeAtKey(fieldId);
115308         });
115309     },
115310
115311     getFloatingButtons: function() {
115312         var me = this,
115313             cssPrefix = Ext.baseCSSPrefix,
115314             btnsCss = cssPrefix + 'grid-row-editor-buttons',
115315             plugin = me.editingPlugin,
115316             btns;
115317
115318         if (!me.floatingButtons) {
115319             btns = me.floatingButtons = Ext.create('Ext.Container', {
115320                 renderTpl: [
115321                     '<div class="{baseCls}-ml"></div>',
115322                     '<div class="{baseCls}-mr"></div>',
115323                     '<div class="{baseCls}-bl"></div>',
115324                     '<div class="{baseCls}-br"></div>',
115325                     '<div class="{baseCls}-bc"></div>'
115326                 ],
115327
115328                 renderTo: me.el,
115329                 baseCls: btnsCss,
115330                 layout: {
115331                     type: 'hbox',
115332                     align: 'middle'
115333                 },
115334                 defaults: {
115335                     margins: '0 1 0 1'
115336                 },
115337                 items: [{
115338                     itemId: 'update',
115339                     flex: 1,
115340                     xtype: 'button',
115341                     handler: plugin.completeEdit,
115342                     scope: plugin,
115343                     text: me.saveBtnText,
115344                     disabled: !me.isValid
115345                 }, {
115346                     flex: 1,
115347                     xtype: 'button',
115348                     handler: plugin.cancelEdit,
115349                     scope: plugin,
115350                     text: me.cancelBtnText
115351                 }]
115352             });
115353
115354             // Prevent from bubbling click events to the grid view
115355             me.mon(btns.el, {
115356                 // BrowserBug: Opera 11.01
115357                 //   causes the view to scroll when a button is focused from mousedown
115358                 mousedown: Ext.emptyFn,
115359                 click: Ext.emptyFn,
115360                 stopEvent: true
115361             });
115362         }
115363         return me.floatingButtons;
115364     },
115365
115366     reposition: function(animateConfig) {
115367         var me = this,
115368             context = me.context,
115369             row = context && Ext.get(context.row),
115370             btns = me.getFloatingButtons(),
115371             btnEl = btns.el,
115372             grid = me.editingPlugin.grid,
115373             viewEl = grid.view.el,
115374             scroller = grid.verticalScroller,
115375
115376             // always get data from ColumnModel as its what drives
115377             // the GridView's sizing
115378             mainBodyWidth = grid.headerCt.getFullWidth(),
115379             scrollerWidth = grid.getWidth(),
115380
115381             // use the minimum as the columns may not fill up the entire grid
115382             // width
115383             width = Math.min(mainBodyWidth, scrollerWidth),
115384             scrollLeft = grid.view.el.dom.scrollLeft,
115385             btnWidth = btns.getWidth(),
115386             left = (width - btnWidth) / 2 + scrollLeft,
115387             y, rowH, newHeight,
115388
115389             invalidateScroller = function() {
115390                 if (scroller) {
115391                     scroller.invalidate();
115392                     btnEl.scrollIntoView(viewEl, false);
115393                 }
115394                 if (animateConfig && animateConfig.callback) {
115395                     animateConfig.callback.call(animateConfig.scope || me);
115396                 }
115397             };
115398
115399         // need to set both top/left
115400         if (row && Ext.isElement(row.dom)) {
115401             // Bring our row into view if necessary, so a row editor that's already
115402             // visible and animated to the row will appear smooth
115403             row.scrollIntoView(viewEl, false);
115404
115405             // Get the y position of the row relative to its top-most static parent.
115406             // offsetTop will be relative to the table, and is incorrect
115407             // when mixed with certain grid features (e.g., grouping).
115408             y = row.getXY()[1] - 5;
115409             rowH = row.getHeight();
115410             newHeight = rowH + 10;
115411
115412             // IE doesn't set the height quite right.
115413             // This isn't a border-box issue, it even happens
115414             // in IE8 and IE7 quirks.
115415             // TODO: Test in IE9!
115416             if (Ext.isIE) {
115417                 newHeight += 2;
115418             }
115419
115420             // Set editor height to match the row height
115421             if (me.getHeight() != newHeight) {
115422                 me.setHeight(newHeight);
115423                 me.el.setLeft(0);
115424             }
115425
115426             if (animateConfig) {
115427                 var animObj = {
115428                     to: {
115429                         y: y
115430                     },
115431                     duration: animateConfig.duration || 125,
115432                     listeners: {
115433                         afteranimate: function() {
115434                             invalidateScroller();
115435                             y = row.getXY()[1] - 5;
115436                             me.el.setY(y);
115437                         }
115438                     }
115439                 };
115440                 me.animate(animObj);
115441             } else {
115442                 me.el.setY(y);
115443                 invalidateScroller();
115444             }
115445         }
115446         if (me.getWidth() != mainBodyWidth) {
115447             me.setWidth(mainBodyWidth);
115448         }
115449         btnEl.setLeft(left);
115450     },
115451
115452     getEditor: function(fieldInfo) {
115453         var me = this;
115454
115455         if (Ext.isNumber(fieldInfo)) {
115456             // Query only form fields. This just future-proofs us in case we add
115457             // other components to RowEditor later on.  Don't want to mess with
115458             // indices.
115459             return me.query('>[isFormField]')[fieldInfo];
115460         } else if (fieldInfo instanceof Ext.grid.column.Column) {
115461             return fieldInfo.getEditor();
115462         }
115463     },
115464
115465     removeField: function(field) {
115466         var me = this;
115467
115468         // Incase we pass a column instead, which is fine
115469         field = me.getEditor(field);
115470         me.mun(field, 'validitychange', me.onValidityChange, me);
115471
115472         // Remove field/column from our mapping, which will fire the event to
115473         // remove the field from our container
115474         me.columns.removeKey(field.id);
115475     },
115476
115477     setField: function(column) {
115478         var me = this,
115479             field;
115480
115481         if (Ext.isArray(column)) {
115482             Ext.Array.forEach(column, me.setField, me);
115483             return;
115484         }
115485
115486         // Get a default display field if necessary
115487         field = column.getEditor(null, {
115488             xtype: 'displayfield',
115489             // Default display fields will not return values. This is done because
115490             // the display field will pick up column renderers from the grid.
115491             getModelData: function() {
115492                 return null;
115493             }
115494         });
115495         field.margins = '0 0 0 2';
115496         field.setWidth(column.getDesiredWidth() - 2);
115497         me.mon(field, 'change', me.onFieldChange, me);
115498
115499         // Maintain mapping of fields-to-columns
115500         // This will fire events that maintain our container items
115501         me.columns.add(field.id, column);
115502         if (column.hidden) {
115503             me.onColumnHide(column);
115504         }
115505         if (me.isVisible() && me.context) {
115506             me.renderColumnData(field, me.context.record);
115507         }
115508     },
115509
115510     loadRecord: function(record) {
115511         var me = this,
115512             form = me.getForm();
115513         form.loadRecord(record);
115514         if (form.isValid()) {
115515             me.hideToolTip();
115516         } else {
115517             me.showToolTip();
115518         }
115519
115520         // render display fields so they honor the column renderer/template
115521         Ext.Array.forEach(me.query('>displayfield'), function(field) {
115522             me.renderColumnData(field, record);
115523         }, me);
115524     },
115525
115526     renderColumnData: function(field, record) {
115527         var me = this,
115528             grid = me.editingPlugin.grid,
115529             headerCt = grid.headerCt,
115530             view = grid.view,
115531             store = view.store,
115532             column = me.columns.get(field.id),
115533             value = record.get(column.dataIndex);
115534
115535         // honor our column's renderer (TemplateHeader sets renderer for us!)
115536         if (column.renderer) {
115537             var metaData = { tdCls: '', style: '' },
115538                 rowIdx = store.indexOf(record),
115539                 colIdx = headerCt.getHeaderIndex(column);
115540
115541             value = column.renderer.call(
115542                 column.scope || headerCt.ownerCt,
115543                 value,
115544                 metaData,
115545                 record,
115546                 rowIdx,
115547                 colIdx,
115548                 store,
115549                 view
115550             );
115551         }
115552
115553         field.setRawValue(value);
115554         field.resetOriginalValue();
115555     },
115556
115557     beforeEdit: function() {
115558         var me = this;
115559
115560         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
115561             me.showToolTip();
115562             return false;
115563         }
115564     },
115565
115566     /**
115567      * Start editing the specified grid at the specified position.
115568      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
115569      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
115570      */
115571     startEdit: function(record, columnHeader) {
115572         var me = this,
115573             grid = me.editingPlugin.grid,
115574             view = grid.getView(),
115575             store = grid.store,
115576             context = me.context = Ext.apply(me.editingPlugin.context, {
115577                 view: grid.getView(),
115578                 store: store
115579             });
115580
115581         // make sure our row is selected before editing
115582         context.grid.getSelectionModel().select(record);
115583
115584         // Reload the record data
115585         me.loadRecord(record);
115586
115587         if (!me.isVisible()) {
115588             me.show();
115589             me.focusContextCell();
115590         } else {
115591             me.reposition({
115592                 callback: this.focusContextCell
115593             });
115594         }
115595     },
115596
115597     // Focus the cell on start edit based upon the current context
115598     focusContextCell: function() {
115599         var field = this.getEditor(this.context.colIdx);
115600         if (field && field.focus) {
115601             field.focus();
115602         }
115603     },
115604
115605     cancelEdit: function() {
115606         var me = this,
115607             form = me.getForm();
115608
115609         me.hide();
115610         form.clearInvalid();
115611         form.reset();
115612     },
115613
115614     completeEdit: function() {
115615         var me = this,
115616             form = me.getForm();
115617
115618         if (!form.isValid()) {
115619             return;
115620         }
115621
115622         form.updateRecord(me.context.record);
115623         me.hide();
115624         return true;
115625     },
115626
115627     onShow: function() {
115628         var me = this;
115629         me.callParent(arguments);
115630         me.reposition();
115631     },
115632
115633     onHide: function() {
115634         var me = this;
115635         me.callParent(arguments);
115636         me.hideToolTip();
115637         me.invalidateScroller();
115638         if (me.context) {
115639             me.context.view.focus();
115640             me.context = null;
115641         }
115642     },
115643
115644     isDirty: function() {
115645         var me = this,
115646             form = me.getForm();
115647         return form.isDirty();
115648     },
115649
115650     getToolTip: function() {
115651         var me = this,
115652             tip;
115653
115654         if (!me.tooltip) {
115655             tip = me.tooltip = Ext.createWidget('tooltip', {
115656                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
115657                 title: me.errorsText,
115658                 autoHide: false,
115659                 closable: true,
115660                 closeAction: 'disable',
115661                 anchor: 'left'
115662             });
115663         }
115664         return me.tooltip;
115665     },
115666
115667     hideToolTip: function() {
115668         var me = this,
115669             tip = me.getToolTip();
115670         if (tip.rendered) {
115671             tip.disable();
115672         }
115673         me.hiddenTip = false;
115674     },
115675
115676     showToolTip: function() {
115677         var me = this,
115678             tip = me.getToolTip(),
115679             context = me.context,
115680             row = Ext.get(context.row),
115681             viewEl = context.grid.view.el;
115682
115683         tip.setTarget(row);
115684         tip.showAt([-10000, -10000]);
115685         tip.body.update(me.getErrors());
115686         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
115687         me.repositionTip();
115688         tip.doLayout();
115689         tip.enable();
115690     },
115691
115692     repositionTip: function() {
115693         var me = this,
115694             tip = me.getToolTip(),
115695             context = me.context,
115696             row = Ext.get(context.row),
115697             viewEl = context.grid.view.el,
115698             viewHeight = viewEl.getHeight(),
115699             viewTop = me.lastScrollTop,
115700             viewBottom = viewTop + viewHeight,
115701             rowHeight = row.getHeight(),
115702             rowTop = row.dom.offsetTop,
115703             rowBottom = rowTop + rowHeight;
115704
115705         if (rowBottom > viewTop && rowTop < viewBottom) {
115706             tip.show();
115707             me.hiddenTip = false;
115708         } else {
115709             tip.hide();
115710             me.hiddenTip = true;
115711         }
115712     },
115713
115714     getErrors: function() {
115715         var me = this,
115716             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
115717             errors = [];
115718
115719         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
115720             errors = errors.concat(
115721                 Ext.Array.map(field.getErrors(), function(e) {
115722                     return '<li>' + e + '</li>';
115723                 })
115724             );
115725         }, me);
115726
115727         return dirtyText + '<ul>' + errors.join('') + '</ul>';
115728     },
115729
115730     invalidateScroller: function() {
115731         var me = this,
115732             context = me.context,
115733             scroller = context.grid.verticalScroller;
115734
115735         if (scroller) {
115736             scroller.invalidate();
115737         }
115738     }
115739 });
115740 /**
115741  * @class Ext.grid.header.Container
115742  * @extends Ext.container.Container
115743  *
115744  * Container which holds headers and is docked at the top or bottom of a TablePanel.
115745  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
115746  * As headers are hidden, moved or resized the headercontainer is responsible for
115747  * triggering changes within the view.
115748  */
115749 Ext.define('Ext.grid.header.Container', {
115750     extend: 'Ext.container.Container',
115751     uses: [
115752         'Ext.grid.ColumnLayout',
115753         'Ext.grid.column.Column',
115754         'Ext.menu.Menu',
115755         'Ext.menu.CheckItem',
115756         'Ext.menu.Separator',
115757         'Ext.grid.plugin.HeaderResizer',
115758         'Ext.grid.plugin.HeaderReorderer'
115759     ],
115760     border: true,
115761
115762     alias: 'widget.headercontainer',
115763
115764     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
115765     dock: 'top',
115766
115767     /**
115768      * @cfg {Number} weight
115769      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
115770      * This is so that it has more priority over things like toolbars.
115771      */
115772     weight: 100,
115773     defaultType: 'gridcolumn',
115774     /**
115775      * @cfg {Number} defaultWidth
115776      * Width of the header if no width or flex is specified. Defaults to 100.
115777      */
115778     defaultWidth: 100,
115779
115780
115781     sortAscText: 'Sort Ascending',
115782     sortDescText: 'Sort Descending',
115783     sortClearText: 'Clear Sort',
115784     columnsText: 'Columns',
115785
115786     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
115787     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
115788     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
115789
115790     // private; will probably be removed by 4.0
115791     triStateSort: false,
115792
115793     ddLock: false,
115794
115795     dragging: false,
115796
115797     /**
115798      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
115799      * @type Boolean
115800      * @property isGroupHeader
115801      */
115802
115803     /**
115804      * @cfg {Boolean} sortable
115805      * Provides the default sortable state for all Headers within this HeaderContainer.
115806      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
115807      * shared across every header and therefore turning it off will remove the menu
115808      * items for every header.
115809      */
115810     sortable: true,
115811
115812     initComponent: function() {
115813         var me = this;
115814
115815         me.headerCounter = 0;
115816         me.plugins = me.plugins || [];
115817
115818         // TODO: Pass in configurations to turn on/off dynamic
115819         //       resizing and disable resizing all together
115820
115821         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
115822         // Nested Group Headers are themselves HeaderContainers
115823         if (!me.isHeader) {
115824             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
115825             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
115826             if (!me.enableColumnResize) {
115827                 me.resizer.disable();
115828             }
115829             if (!me.enableColumnMove) {
115830                 me.reorderer.disable();
115831             }
115832             me.plugins.push(me.reorderer, me.resizer);
115833         }
115834
115835         // Base headers do not need a box layout
115836         if (me.isHeader && !me.items) {
115837             me.layout = 'auto';
115838         }
115839         // HeaderContainer and Group header needs a gridcolumn layout.
115840         else {
115841             me.layout = {
115842                 type: 'gridcolumn',
115843                 availableSpaceOffset: me.availableSpaceOffset,
115844                 align: 'stretchmax',
115845                 resetStretch: true
115846             };
115847         }
115848         me.defaults = me.defaults || {};
115849         Ext.applyIf(me.defaults, {
115850             width: me.defaultWidth,
115851             triStateSort: me.triStateSort,
115852             sortable: me.sortable
115853         });
115854         me.callParent();
115855         me.addEvents(
115856             /**
115857              * @event columnresize
115858              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115859              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115860              * @param {Number} width
115861              */
115862             'columnresize',
115863
115864             /**
115865              * @event headerclick
115866              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115867              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115868              * @param {Ext.EventObject} e
115869              * @param {HTMLElement} t
115870              */
115871             'headerclick',
115872
115873             /**
115874              * @event headertriggerclick
115875              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115876              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115877              * @param {Ext.EventObject} e
115878              * @param {HTMLElement} t
115879              */
115880             'headertriggerclick',
115881
115882             /**
115883              * @event columnmove
115884              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115885              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115886              * @param {Number} fromIdx
115887              * @param {Number} toIdx
115888              */
115889             'columnmove',
115890             /**
115891              * @event columnhide
115892              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115893              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115894              */
115895             'columnhide',
115896             /**
115897              * @event columnshow
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              */
115901             'columnshow',
115902             /**
115903              * @event sortchange
115904              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115905              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115906              * @param {String} direction
115907              */
115908             'sortchange',
115909             /**
115910              * @event menucreate
115911              * Fired immediately after the column header menu is created.
115912              * @param {Ext.grid.header.Container} ct This instance
115913              * @param {Ext.menu.Menu} menu The Menu that was created
115914              */
115915             'menucreate'
115916         );
115917     },
115918
115919     onDestroy: function() {
115920         Ext.destroy(this.resizer, this.reorderer);
115921         this.callParent();
115922     },
115923     
115924     applyDefaults: function(config){
115925         /*
115926          * Ensure header.Container defaults don't get applied to a RowNumberer 
115927          * if an xtype is supplied. This isn't an ideal solution however it's 
115928          * much more likely that a RowNumberer with no options will be created, 
115929          * wanting to use the defaults specified on the class as opposed to 
115930          * those setup on the Container.
115931          */
115932         if (config && !config.isComponent && config.xtype == 'rownumberer') {
115933             return config;
115934         }
115935         return this.callParent([config]);
115936     },
115937
115938     applyColumnsState: function(columns) {
115939         if (!columns || !columns.length) {
115940             return;
115941         }
115942
115943         var me = this,
115944             i = 0,
115945             index,
115946             col;
115947
115948         Ext.each(columns, function (columnState) {
115949             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
115950             if (col) {
115951                 index = me.items.indexOf(col);
115952                 if (i !== index) {
115953                     me.moveHeader(index, i);
115954                 }
115955
115956                 if (col.applyColumnState) {
115957                     col.applyColumnState(columnState);
115958                 }
115959                 ++i;
115960             }
115961         });
115962     },
115963
115964     getColumnsState: function () {
115965         var me = this,
115966             columns = [],
115967             state;
115968
115969         me.items.each(function (col) {
115970             state = col.getColumnState && col.getColumnState();
115971             if (state) {
115972                 columns.push(state);
115973             }
115974         });
115975
115976         return columns;
115977     },
115978
115979     // Invalidate column cache on add
115980     // We cannot refresh the View on every add because this method is called
115981     // when the HeaderDropZone moves Headers around, that will also refresh the view
115982     onAdd: function(c) {
115983         var me = this;
115984         if (!c.headerId) {
115985             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
115986         }
115987         me.callParent(arguments);
115988         me.purgeCache();
115989     },
115990
115991     // Invalidate column cache on remove
115992     // We cannot refresh the View on every remove because this method is called
115993     // when the HeaderDropZone moves Headers around, that will also refresh the view
115994     onRemove: function(c) {
115995         var me = this;
115996         me.callParent(arguments);
115997         me.purgeCache();
115998     },
115999
116000     afterRender: function() {
116001         this.callParent();
116002         var store   = this.up('[store]').store,
116003             sorters = store.sorters,
116004             first   = sorters.first(),
116005             hd;
116006
116007         if (first) {
116008             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
116009             if (hd) {
116010                 hd.setSortState(first.direction, false, true);
116011             }
116012         }
116013     },
116014
116015     afterLayout: function() {
116016         if (!this.isHeader) {
116017             var me = this,
116018                 topHeaders = me.query('>gridcolumn:not([hidden])'),
116019                 viewEl,
116020                 firstHeaderEl,
116021                 lastHeaderEl;
116022
116023             me.callParent(arguments);
116024
116025             if (topHeaders.length) {
116026                 firstHeaderEl = topHeaders[0].el;
116027                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
116028                     if (me.pastFirstHeaderEl) {
116029                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
116030                     }
116031                     firstHeaderEl.addCls(me.firstHeaderCls);
116032                     me.pastFirstHeaderEl = firstHeaderEl;
116033                 }
116034
116035                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
116036                 if (lastHeaderEl !== me.pastLastHeaderEl) {
116037                     if (me.pastLastHeaderEl) {
116038                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
116039                     }
116040                     lastHeaderEl.addCls(me.lastHeaderCls);
116041                     me.pastLastHeaderEl = lastHeaderEl;
116042                 }
116043             }
116044         }
116045
116046     },
116047
116048     onHeaderShow: function(header, preventLayout) {
116049         // Pass up to the GridSection
116050         var me = this,
116051             gridSection = me.ownerCt,
116052             menu = me.getMenu(),
116053             topItems, topItemsVisible,
116054             colCheckItem,
116055             itemToEnable,
116056             len, i;
116057
116058         if (menu) {
116059
116060             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116061             if (colCheckItem) {
116062                 colCheckItem.setChecked(true, true);
116063             }
116064
116065             // There's more than one header visible, and we've disabled some checked items... re-enable them
116066             topItems = menu.query('#columnItem>menucheckitem[checked]');
116067             topItemsVisible = topItems.length;
116068             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
116069                 if (topItemsVisible == 1) {
116070                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
116071                 }
116072                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
116073                     itemToEnable = me.disabledMenuItems[i];
116074                     if (!itemToEnable.isDestroyed) {
116075                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
116076                     }
116077                 }
116078                 if (topItemsVisible == 1) {
116079                     me.disabledMenuItems = topItems;
116080                 } else {
116081                     me.disabledMenuItems = [];
116082                 }
116083             }
116084         }
116085
116086         // Only update the grid UI when we are notified about base level Header shows;
116087         // Group header shows just cause a layout of the HeaderContainer
116088         if (!header.isGroupHeader) {
116089             if (me.view) {
116090                 me.view.onHeaderShow(me, header, true);
116091             }
116092             if (gridSection) {
116093                 gridSection.onHeaderShow(me, header);
116094             }
116095         }
116096         me.fireEvent('columnshow', me, header);
116097
116098         // The header's own hide suppresses cascading layouts, so lay the headers out now
116099         if (preventLayout !== true) {
116100             me.doLayout();
116101         }
116102     },
116103
116104     doComponentLayout: function(){
116105         var me = this;
116106         if (me.view && me.view.saveScrollState) {
116107             me.view.saveScrollState();
116108         }
116109         me.callParent(arguments);
116110         if (me.view && me.view.restoreScrollState) {
116111             me.view.restoreScrollState();
116112         }
116113     },
116114
116115     onHeaderHide: function(header, suppressLayout) {
116116         // Pass up to the GridSection
116117         var me = this,
116118             gridSection = me.ownerCt,
116119             menu = me.getMenu(),
116120             colCheckItem;
116121
116122         if (menu) {
116123
116124             // If the header was hidden programmatically, sync the Menu state
116125             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116126             if (colCheckItem) {
116127                 colCheckItem.setChecked(false, true);
116128             }
116129             me.setDisabledItems();
116130         }
116131
116132         // Only update the UI when we are notified about base level Header hides;
116133         if (!header.isGroupHeader) {
116134             if (me.view) {
116135                 me.view.onHeaderHide(me, header, true);
116136             }
116137             if (gridSection) {
116138                 gridSection.onHeaderHide(me, header);
116139             }
116140
116141             // The header's own hide suppresses cascading layouts, so lay the headers out now
116142             if (!suppressLayout) {
116143                 me.doLayout();
116144             }
116145         }
116146         me.fireEvent('columnhide', me, header);
116147     },
116148
116149     setDisabledItems: function(){
116150         var me = this,
116151             menu = me.getMenu(),
116152             i = 0,
116153             len,
116154             itemsToDisable,
116155             itemToDisable;
116156
116157         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
116158         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
116159         if ((itemsToDisable.length === 1)) {
116160             if (!me.disabledMenuItems) {
116161                 me.disabledMenuItems = [];
116162             }
116163
116164             // If down to only one column visible, also disable any descendant checkitems
116165             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
116166                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
116167             }
116168
116169             len = itemsToDisable.length;
116170             // Disable any further unchecking at any level.
116171             for (i = 0; i < len; i++) {
116172                 itemToDisable = itemsToDisable[i];
116173                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
116174
116175                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
116176                     // setting its correct disablement level.
116177                     itemToDisable.disabled = false;
116178                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
116179                     me.disabledMenuItems.push(itemToDisable);
116180                 }
116181             }
116182         }
116183     },
116184
116185     /**
116186      * Temporarily lock the headerCt. This makes it so that clicking on headers
116187      * don't trigger actions like sorting or opening of the header menu. This is
116188      * done because extraneous events may be fired on the headers after interacting
116189      * with a drag drop operation.
116190      * @private
116191      */
116192     tempLock: function() {
116193         this.ddLock = true;
116194         Ext.Function.defer(function() {
116195             this.ddLock = false;
116196         }, 200, this);
116197     },
116198
116199     onHeaderResize: function(header, w, suppressFocus) {
116200         this.tempLock();
116201         if (this.view && this.view.rendered) {
116202             this.view.onHeaderResize(header, w, suppressFocus);
116203         }
116204     },
116205
116206     onHeaderClick: function(header, e, t) {
116207         this.fireEvent("headerclick", this, header, e, t);
116208     },
116209
116210     onHeaderTriggerClick: function(header, e, t) {
116211         // generate and cache menu, provide ability to cancel/etc
116212         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
116213             this.showMenuBy(t, header);
116214         }
116215     },
116216
116217     showMenuBy: function(t, header) {
116218         var menu = this.getMenu(),
116219             ascItem  = menu.down('#ascItem'),
116220             descItem = menu.down('#descItem'),
116221             sortableMth;
116222
116223         menu.activeHeader = menu.ownerCt = header;
116224         menu.setFloatParent(header);
116225         // TODO: remove coupling to Header's titleContainer el
116226         header.titleContainer.addCls(this.headerOpenCls);
116227
116228         // enable or disable asc & desc menu items based on header being sortable
116229         sortableMth = header.sortable ? 'enable' : 'disable';
116230         if (ascItem) {
116231             ascItem[sortableMth]();
116232         }
116233         if (descItem) {
116234             descItem[sortableMth]();
116235         }
116236         menu.showBy(t);
116237     },
116238
116239     // remove the trigger open class when the menu is hidden
116240     onMenuDeactivate: function() {
116241         var menu = this.getMenu();
116242         // TODO: remove coupling to Header's titleContainer el
116243         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
116244     },
116245
116246     moveHeader: function(fromIdx, toIdx) {
116247
116248         // An automatically expiring lock
116249         this.tempLock();
116250         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
116251     },
116252
116253     purgeCache: function() {
116254         var me = this;
116255         // Delete column cache - column order has changed.
116256         delete me.gridDataColumns;
116257         delete me.hideableColumns;
116258
116259         // Menu changes when columns are moved. It will be recreated.
116260         if (me.menu) {
116261             me.menu.destroy();
116262             delete me.menu;
116263         }
116264     },
116265
116266     onHeaderMoved: function(header, fromIdx, toIdx) {
116267         var me = this,
116268             gridSection = me.ownerCt;
116269
116270         if (gridSection && gridSection.onHeaderMove) {
116271             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
116272         }
116273         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
116274     },
116275
116276     /**
116277      * Gets the menu (and will create it if it doesn't already exist)
116278      * @private
116279      */
116280     getMenu: function() {
116281         var me = this;
116282
116283         if (!me.menu) {
116284             me.menu = Ext.create('Ext.menu.Menu', {
116285                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
116286                 items: me.getMenuItems(),
116287                 listeners: {
116288                     deactivate: me.onMenuDeactivate,
116289                     scope: me
116290                 }
116291             });
116292             me.setDisabledItems();
116293             me.fireEvent('menucreate', me, me.menu);
116294         }
116295         return me.menu;
116296     },
116297
116298     /**
116299      * Returns an array of menu items to be placed into the shared menu
116300      * across all headers in this header container.
116301      * @returns {Array} menuItems
116302      */
116303     getMenuItems: function() {
116304         var me = this,
116305             menuItems = [],
116306             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
116307
116308         if (me.sortable) {
116309             menuItems = [{
116310                 itemId: 'ascItem',
116311                 text: me.sortAscText,
116312                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
116313                 handler: me.onSortAscClick,
116314                 scope: me
116315             },{
116316                 itemId: 'descItem',
116317                 text: me.sortDescText,
116318                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
116319                 handler: me.onSortDescClick,
116320                 scope: me
116321             }];
116322         }
116323         if (hideableColumns && hideableColumns.length) {
116324             menuItems.push('-', {
116325                 itemId: 'columnItem',
116326                 text: me.columnsText,
116327                 cls: Ext.baseCSSPrefix + 'cols-icon',
116328                 menu: hideableColumns
116329             });
116330         }
116331         return menuItems;
116332     },
116333
116334     // sort asc when clicking on item in menu
116335     onSortAscClick: function() {
116336         var menu = this.getMenu(),
116337             activeHeader = menu.activeHeader;
116338
116339         activeHeader.setSortState('ASC');
116340     },
116341
116342     // sort desc when clicking on item in menu
116343     onSortDescClick: function() {
116344         var menu = this.getMenu(),
116345             activeHeader = menu.activeHeader;
116346
116347         activeHeader.setSortState('DESC');
116348     },
116349
116350     /**
116351      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
116352      */
116353     getColumnMenu: function(headerContainer) {
116354         var menuItems = [],
116355             i = 0,
116356             item,
116357             items = headerContainer.query('>gridcolumn[hideable]'),
116358             itemsLn = items.length,
116359             menuItem;
116360
116361         for (; i < itemsLn; i++) {
116362             item = items[i];
116363             menuItem = Ext.create('Ext.menu.CheckItem', {
116364                 text: item.text,
116365                 checked: !item.hidden,
116366                 hideOnClick: false,
116367                 headerId: item.id,
116368                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
116369                 checkHandler: this.onColumnCheckChange,
116370                 scope: this
116371             });
116372             if (itemsLn === 1) {
116373                 menuItem.disabled = true;
116374             }
116375             menuItems.push(menuItem);
116376
116377             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
116378             // then the associated menu item must also be destroyed.
116379             item.on({
116380                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
116381             });
116382         }
116383         return menuItems;
116384     },
116385
116386     onColumnCheckChange: function(checkItem, checked) {
116387         var header = Ext.getCmp(checkItem.headerId);
116388         header[checked ? 'show' : 'hide']();
116389     },
116390
116391     /**
116392      * Get the columns used for generating a template via TableChunker.
116393      * Returns an array of all columns and their
116394      *  - dataIndex
116395      *  - align
116396      *  - width
116397      *  - id
116398      *  - columnId - used to create an identifying CSS class
116399      *  - cls The tdCls configuration from the Column object
116400      *  @private
116401      */
116402     getColumnsForTpl: function(flushCache) {
116403         var cols    = [],
116404             headers   = this.getGridColumns(flushCache),
116405             headersLn = headers.length,
116406             i = 0,
116407             header,
116408             width;
116409
116410         for (; i < headersLn; i++) {
116411             header = headers[i];
116412
116413             if (header.hidden || header.up('headercontainer[hidden=true]')) {
116414                 width = 0;
116415             } else {
116416                 width = header.getDesiredWidth();
116417                 // IE6 and IE7 bug.
116418                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
116419                 // We need to increment the passed with in this case.
116420                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
116421                     width += 1;
116422                 }
116423             }
116424             cols.push({
116425                 dataIndex: header.dataIndex,
116426                 align: header.align,
116427                 width: width,
116428                 id: header.id,
116429                 cls: header.tdCls,
116430                 columnId: header.getItemId()
116431             });
116432         }
116433         return cols;
116434     },
116435
116436     /**
116437      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
116438      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
116439      */
116440     getColumnCount: function() {
116441         return this.getGridColumns().length;
116442     },
116443
116444     /**
116445      * Gets the full width of all columns that are visible.
116446      */
116447     getFullWidth: function(flushCache) {
116448         var fullWidth = 0,
116449             headers     = this.getVisibleGridColumns(flushCache),
116450             headersLn   = headers.length,
116451             i         = 0;
116452
116453         for (; i < headersLn; i++) {
116454             if (!isNaN(headers[i].width)) {
116455                 // use headers getDesiredWidth if its there
116456                 if (headers[i].getDesiredWidth) {
116457                     fullWidth += headers[i].getDesiredWidth();
116458                 // if injected a diff cmp use getWidth
116459                 } else {
116460                     fullWidth += headers[i].getWidth();
116461                 }
116462             }
116463         }
116464         return fullWidth;
116465     },
116466
116467     // invoked internally by a header when not using triStateSorting
116468     clearOtherSortStates: function(activeHeader) {
116469         var headers   = this.getGridColumns(),
116470             headersLn = headers.length,
116471             i         = 0,
116472             oldSortState;
116473
116474         for (; i < headersLn; i++) {
116475             if (headers[i] !== activeHeader) {
116476                 oldSortState = headers[i].sortState;
116477                 // unset the sortstate and dont recurse
116478                 headers[i].setSortState(null, true);
116479                 //if (!silent && oldSortState !== null) {
116480                 //    this.fireEvent('sortchange', this, headers[i], null);
116481                 //}
116482             }
116483         }
116484     },
116485
116486     /**
116487      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
116488      * level, and does not return <i>grouped</i> headers which contain sub headers.
116489      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116490      * @returns {Array}
116491      */
116492     getVisibleGridColumns: function(refreshCache) {
116493         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
116494     },
116495
116496     /**
116497      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
116498      * level, and does not return <i>grouped</i> headers which contain sub headers.
116499      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116500      * @returns {Array}
116501      */
116502     getGridColumns: function(refreshCache) {
116503         var me = this,
116504             result = refreshCache ? null : me.gridDataColumns;
116505
116506         // Not already got the column cache, so collect the base columns
116507         if (!result) {
116508             me.gridDataColumns = result = [];
116509             me.cascade(function(c) {
116510                 if ((c !== me) && !c.isGroupHeader) {
116511                     result.push(c);
116512                 }
116513             });
116514         }
116515
116516         return result;
116517     },
116518
116519     /**
116520      * @private
116521      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
116522      * the header menu should be disabled.
116523      */
116524     getHideableColumns: function(refreshCache) {
116525         var me = this,
116526             result = refreshCache ? null : me.hideableColumns;
116527
116528         if (!result) {
116529             result = me.hideableColumns = me.query('[hideable]');
116530         }
116531         return result;
116532     },
116533
116534     /**
116535      * Get the index of a leaf level header regardless of what the nesting
116536      * structure is.
116537      */
116538     getHeaderIndex: function(header) {
116539         var columns = this.getGridColumns();
116540         return Ext.Array.indexOf(columns, header);
116541     },
116542
116543     /**
116544      * Get a leaf level header by index regardless of what the nesting
116545      * structure is.
116546      */
116547     getHeaderAtIndex: function(index) {
116548         var columns = this.getGridColumns();
116549         return columns[index];
116550     },
116551
116552     /**
116553      * Maps the record data to base it on the header id's.
116554      * This correlates to the markup/template generated by
116555      * TableChunker.
116556      */
116557     prepareData: function(data, rowIdx, record, view, panel) {
116558         var obj       = {},
116559             headers   = this.gridDataColumns || this.getGridColumns(),
116560             headersLn = headers.length,
116561             colIdx    = 0,
116562             header,
116563             headerId,
116564             renderer,
116565             value,
116566             metaData,
116567             store = panel.store;
116568
116569         for (; colIdx < headersLn; colIdx++) {
116570             metaData = {
116571                 tdCls: '',
116572                 style: ''
116573             };
116574             header = headers[colIdx];
116575             headerId = header.id;
116576             renderer = header.renderer;
116577             value = data[header.dataIndex];
116578
116579             // When specifying a renderer as a string, it always resolves
116580             // to Ext.util.Format
116581             if (typeof renderer === "string") {
116582                 header.renderer = renderer = Ext.util.Format[renderer];
116583             }
116584
116585             if (typeof renderer === "function") {
116586                 value = renderer.call(
116587                     header.scope || this.ownerCt,
116588                     value,
116589                     // metadata per cell passing an obj by reference so that
116590                     // it can be manipulated inside the renderer
116591                     metaData,
116592                     record,
116593                     rowIdx,
116594                     colIdx,
116595                     store,
116596                     view
116597                 );
116598             }
116599
116600
116601             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
116602             obj[headerId+'-tdCls'] = metaData.tdCls;
116603             obj[headerId+'-tdAttr'] = metaData.tdAttr;
116604             obj[headerId+'-style'] = metaData.style;
116605             if (value === undefined || value === null || value === '') {
116606                 value = '&#160;';
116607             }
116608             obj[headerId] = value;
116609         }
116610         return obj;
116611     },
116612
116613     expandToFit: function(header) {
116614         if (this.view) {
116615             this.view.expandToFit(header);
116616         }
116617     }
116618 });
116619
116620 /**
116621  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
116622  * both the grid header configuration as well as displaying data within the grid itself. If the
116623  * {@link #columns} configuration is specified, this column will become a column group and can
116624  * contain other columns inside. In general, this class will not be created directly, rather
116625  * an array of column configurations will be passed to the grid:
116626  *
116627  *     @example
116628  *     Ext.create('Ext.data.Store', {
116629  *         storeId:'employeeStore',
116630  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
116631  *         data:[
116632  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
116633  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
116634  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
116635  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
116636  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
116637  *         ]
116638  *     });
116639  *
116640  *     Ext.create('Ext.grid.Panel', {
116641  *         title: 'Column Demo',
116642  *         store: Ext.data.StoreManager.lookup('employeeStore'),
116643  *         columns: [
116644  *             {text: 'First Name',  dataIndex:'firstname'},
116645  *             {text: 'Last Name',  dataIndex:'lastname'},
116646  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
116647  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
116648  *         ],
116649  *         width: 400,
116650  *         renderTo: Ext.getBody()
116651  *     });
116652  *
116653  * # Convenience Subclasses
116654  *
116655  * There are several column subclasses that provide default rendering for various data types
116656  *
116657  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
116658  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
116659  *  - {@link Ext.grid.column.Date}: Renders for date values
116660  *  - {@link Ext.grid.column.Number}: Renders for numeric values
116661  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
116662  *
116663  * # Setting Sizes
116664  *
116665  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
116666  * be given an explicit width value or a flex configuration. If no width is specified the grid will
116667  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
116668  * the width of the child columns, so a width option should not be specified in that case.
116669  *
116670  * # Header Options
116671  *
116672  *  - {@link #text}: Sets the header text for the column
116673  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
116674  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
116675  *  - {@link #menuDisabled}: Disables the column header menu
116676  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
116677  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
116678  *
116679  * # Data Options
116680  *
116681  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
116682  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
116683  */
116684 Ext.define('Ext.grid.column.Column', {
116685     extend: 'Ext.grid.header.Container',
116686     alias: 'widget.gridcolumn',
116687     requires: ['Ext.util.KeyNav'],
116688     alternateClassName: 'Ext.grid.Column',
116689
116690     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
116691
116692     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
116693     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
116694
116695     handleWidth: 5,
116696
116697     sortState: null,
116698
116699     possibleSortStates: ['ASC', 'DESC'],
116700
116701     renderTpl:
116702         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
116703             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
116704                 '{text}' +
116705             '</span>' +
116706             '<tpl if="!values.menuDisabled">'+
116707                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
116708             '</tpl>' +
116709         '</div>',
116710
116711     /**
116712      * @cfg {Object[]} columns
116713      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
116714      * `columns` config.
116715      *
116716      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
116717      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
116718      */
116719
116720     /**
116721      * @cfg {String} dataIndex
116722      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
116723      * which to draw the column's value. **Required.**
116724      */
116725     dataIndex: null,
116726
116727     /**
116728      * @cfg {String} text
116729      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
116730      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
116731      */
116732     text: '&#160;',
116733
116734     /**
116735      * @cfg {Boolean} sortable
116736      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
116737      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
116738      */
116739     sortable: true,
116740
116741     /**
116742      * @cfg {Boolean} groupable
116743      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
116744      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
116745      * disable (but still show) the group option in the header menu for the column.
116746      */
116747
116748     /**
116749      * @cfg {Boolean} fixed
116750      * @deprecated.
116751      * True to prevent the column from being resizable.
116752      */
116753
116754     /**
116755      * @cfg {Boolean} resizable
116756      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
116757      */
116758
116759     /**
116760      * @cfg {Boolean} hideable
116761      * False to prevent the user from hiding this column. Defaults to true.
116762      */
116763     hideable: true,
116764
116765     /**
116766      * @cfg {Boolean} menuDisabled
116767      * True to disable the column header menu containing sort/hide options. Defaults to false.
116768      */
116769     menuDisabled: false,
116770
116771     /**
116772      * @cfg {Function} renderer
116773      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
116774      * before it is rendered. Example:
116775      *
116776      *     {
116777      *         renderer: function(value){
116778      *             if (value === 1) {
116779      *                 return '1 person';
116780      *             }
116781      *             return value + ' people';
116782      *         }
116783      *     }
116784      *
116785      * @cfg {Object} renderer.value The data value for the current cell
116786      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
116787      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
116788      * @cfg {Ext.data.Model} renderer.record The record for the current row
116789      * @cfg {Number} renderer.rowIndex The index of the current row
116790      * @cfg {Number} renderer.colIndex The index of the current column
116791      * @cfg {Ext.data.Store} renderer.store The data store
116792      * @cfg {Ext.view.View} renderer.view The current view
116793      * @cfg {String} renderer.return The HTML string to be rendered.
116794      */
116795     renderer: false,
116796
116797     /**
116798      * @cfg {String} align
116799      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
116800      */
116801     align: 'left',
116802
116803     /**
116804      * @cfg {Boolean} draggable
116805      * False to disable drag-drop reordering of this column. Defaults to true.
116806      */
116807     draggable: true,
116808
116809     // Header does not use the typical ComponentDraggable class and therefore we
116810     // override this with an emptyFn. It is controlled at the HeaderDragZone.
116811     initDraggable: Ext.emptyFn,
116812
116813     /**
116814      * @cfg {String} tdCls
116815      * A CSS class names to apply to the table cells for this column.
116816      */
116817
116818     /**
116819      * @cfg {Object/String} editor
116820      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
116821      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
116822      */
116823
116824     /**
116825      * @cfg {Object/String} field
116826      * Alias for {@link #editor}.
116827      * @deprecated 4.0.5 Use {@link #editor} instead.
116828      */
116829
116830     /**
116831      * @property {Ext.Element} triggerEl
116832      * Element that acts as button for column header dropdown menu.
116833      */
116834
116835     /**
116836      * @property {Ext.Element} textEl
116837      * Element that contains the text in column header.
116838      */
116839
116840     /**
116841      * @private
116842      * Set in this class to identify, at runtime, instances which are not instances of the
116843      * HeaderContainer base class, but are in fact, the subclass: Header.
116844      */
116845     isHeader: true,
116846
116847     initComponent: function() {
116848         var me = this,
116849             i,
116850             len,
116851             item;
116852
116853         if (Ext.isDefined(me.header)) {
116854             me.text = me.header;
116855             delete me.header;
116856         }
116857
116858         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
116859         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
116860         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
116861         if (me.flex) {
116862             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
116863         }
116864         // Non-flexed Headers may never be squeezed in the event of a shortfall so
116865         // always set their minWidth to their current width.
116866         else {
116867             me.minWidth = me.width;
116868         }
116869
116870         if (!me.triStateSort) {
116871             me.possibleSortStates.length = 2;
116872         }
116873
116874         // A group header; It contains items which are themselves Headers
116875         if (Ext.isDefined(me.columns)) {
116876             me.isGroupHeader = true;
116877
116878
116879             // The headers become child items
116880             me.items = me.columns;
116881             delete me.columns;
116882             delete me.flex;
116883             me.width = 0;
116884
116885             // Acquire initial width from sub headers
116886             for (i = 0, len = me.items.length; i < len; i++) {
116887                 item = me.items[i];
116888                 if (!item.hidden) {
116889                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
116890                 }
116891             }
116892             me.minWidth = me.width;
116893
116894             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
116895             me.sortable = false;
116896             me.resizable = false;
116897             me.align = 'center';
116898         }
116899
116900         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
116901
116902         // Initialize as a HeaderContainer
116903         me.callParent(arguments);
116904     },
116905
116906     onAdd: function(childHeader) {
116907         childHeader.isSubHeader = true;
116908         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
116909         this.callParent(arguments);
116910     },
116911
116912     onRemove: function(childHeader) {
116913         childHeader.isSubHeader = false;
116914         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
116915         this.callParent(arguments);
116916     },
116917
116918     initRenderData: function() {
116919         var me = this;
116920
116921         Ext.applyIf(me.renderData, {
116922             text: me.text,
116923             menuDisabled: me.menuDisabled
116924         });
116925         return me.callParent(arguments);
116926     },
116927
116928     applyColumnState: function (state) {
116929         var me = this,
116930             defined = Ext.isDefined;
116931             
116932         // apply any columns
116933         me.applyColumnsState(state.columns);
116934
116935         // Only state properties which were saved should be restored.
116936         // (Only user-changed properties were saved by getState)
116937         if (defined(state.hidden)) {
116938             me.hidden = state.hidden;
116939         }
116940         if (defined(state.locked)) {
116941             me.locked = state.locked;
116942         }
116943         if (defined(state.sortable)) {
116944             me.sortable = state.sortable;
116945         }
116946         if (defined(state.width)) {
116947             delete me.flex;
116948             me.width = state.width;
116949         } else if (defined(state.flex)) {
116950             delete me.width;
116951             me.flex = state.flex;
116952         }
116953     },
116954
116955     getColumnState: function () {
116956         var me = this,
116957             columns = [],
116958             state = {
116959                 id: me.headerId
116960             };
116961
116962         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
116963         
116964         if (me.isGroupHeader) {
116965             me.items.each(function(column){
116966                 columns.push(column.getColumnState());
116967             });
116968             if (columns.length) {
116969                 state.columns = columns;
116970             }
116971         } else if (me.isSubHeader && me.ownerCt.hidden) {
116972             // don't set hidden on the children so they can auto height
116973             delete me.hidden;
116974         }
116975
116976         if ('width' in state) {
116977             delete state.flex; // width wins
116978         }
116979         return state;
116980     },
116981
116982     /**
116983      * Sets the header text for this Column.
116984      * @param {String} text The header to display on this Column.
116985      */
116986     setText: function(text) {
116987         this.text = text;
116988         if (this.rendered) {
116989             this.textEl.update(text);
116990         }
116991     },
116992
116993     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
116994     // Group Headers are themselves HeaderContainers
116995     getOwnerHeaderCt: function() {
116996         return this.up(':not([isHeader])');
116997     },
116998
116999     /**
117000      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
117001      * is a group column, it returns `false`.
117002      * @return {Number}
117003      */
117004     getIndex: function() {
117005         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
117006     },
117007
117008     onRender: function() {
117009         var me = this,
117010             grid = me.up('tablepanel');
117011
117012         // Disable the menu if there's nothing to show in the menu, ie:
117013         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
117014         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
117015             me.menuDisabled = true;
117016         }
117017         me.callParent(arguments);
117018     },
117019
117020     afterRender: function() {
117021         var me = this,
117022             el = me.el;
117023
117024         me.callParent(arguments);
117025
117026         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
117027
117028         me.mon(el, {
117029             click:     me.onElClick,
117030             dblclick:  me.onElDblClick,
117031             scope:     me
117032         });
117033
117034         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
117035         // must be fixed when focus management will be implemented.
117036         if (!Ext.isIE8 || !Ext.isStrict) {
117037             me.mon(me.getFocusEl(), {
117038                 focus: me.onTitleMouseOver,
117039                 blur: me.onTitleMouseOut,
117040                 scope: me
117041             });
117042         }
117043
117044         me.mon(me.titleContainer, {
117045             mouseenter:  me.onTitleMouseOver,
117046             mouseleave:  me.onTitleMouseOut,
117047             scope:      me
117048         });
117049
117050         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
117051             enter: me.onEnterKey,
117052             down: me.onDownKey,
117053             scope: me
117054         });
117055     },
117056
117057     /**
117058      * Sets the width of this Column.
117059      * @param {Number} width New width.
117060      */
117061     setWidth: function(width, /* private - used internally */ doLayout) {
117062         var me = this,
117063             headerCt = me.ownerCt,
117064             siblings,
117065             len, i,
117066             oldWidth = me.getWidth(),
117067             groupWidth = 0,
117068             sibling;
117069
117070         if (width !== oldWidth) {
117071             me.oldWidth = oldWidth;
117072
117073             // Non-flexed Headers may never be squeezed in the event of a shortfall so
117074             // always set the minWidth to their current width.
117075             me.minWidth = me.width = width;
117076
117077             // Bubble size changes upwards to group headers
117078             if (headerCt.isGroupHeader) {
117079                 siblings = headerCt.items.items;
117080                 len = siblings.length;
117081
117082                 for (i = 0; i < len; i++) {
117083                     sibling = siblings[i];
117084                     if (!sibling.hidden) {
117085                         groupWidth += (sibling === me) ? width : sibling.getWidth();
117086                     }
117087                 }
117088                 headerCt.setWidth(groupWidth, doLayout);
117089             } else if (doLayout !== false) {
117090                 // Allow the owning Container to perform the sizing
117091                 headerCt.doLayout();
117092             }
117093         }
117094     },
117095
117096     afterComponentLayout: function(width, height) {
117097         var me = this,
117098             ownerHeaderCt = this.getOwnerHeaderCt();
117099
117100         me.callParent(arguments);
117101
117102         // Only changes at the base level inform the grid's HeaderContainer which will update the View
117103         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
117104         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
117105         if (width && !me.isGroupHeader && ownerHeaderCt) {
117106             ownerHeaderCt.onHeaderResize(me, width, true);
117107         }
117108         if (me.oldWidth && (width !== me.oldWidth)) {
117109             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
117110         }
117111         delete me.oldWidth;
117112     },
117113
117114     // private
117115     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
117116     // Total available header height must be passed to enable padding for inner elements to be calculated.
117117     setPadding: function(headerHeight) {
117118         var me = this,
117119             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
117120
117121         // Top title containing element must stretch to match height of sibling group headers
117122         if (!me.isGroupHeader) {
117123             if (me.titleContainer.getHeight() < headerHeight) {
117124                 me.titleContainer.dom.style.height = headerHeight + 'px';
117125             }
117126         }
117127         headerHeight = me.titleContainer.getViewSize().height;
117128
117129         // Vertically center the header text in potentially vertically stretched header
117130         if (lineHeight) {
117131             me.titleContainer.setStyle({
117132                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
117133             });
117134         }
117135
117136         // Only IE needs this
117137         if (Ext.isIE && me.triggerEl) {
117138             me.triggerEl.setHeight(headerHeight);
117139         }
117140     },
117141
117142     onDestroy: function() {
117143         var me = this;
117144         // force destroy on the textEl, IE reports a leak
117145         Ext.destroy(me.textEl, me.keyNav);
117146         delete me.keyNav;
117147         me.callParent(arguments);
117148     },
117149
117150     onTitleMouseOver: function() {
117151         this.titleContainer.addCls(this.hoverCls);
117152     },
117153
117154     onTitleMouseOut: function() {
117155         this.titleContainer.removeCls(this.hoverCls);
117156     },
117157
117158     onDownKey: function(e) {
117159         if (this.triggerEl) {
117160             this.onElClick(e, this.triggerEl.dom || this.el.dom);
117161         }
117162     },
117163
117164     onEnterKey: function(e) {
117165         this.onElClick(e, this.el.dom);
117166     },
117167
117168     /**
117169      * @private
117170      * Double click
117171      * @param e
117172      * @param t
117173      */
117174     onElDblClick: function(e, t) {
117175         var me = this,
117176             ownerCt = me.ownerCt;
117177         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
117178             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
117179         }
117180     },
117181
117182     onElClick: function(e, t) {
117183
117184         // The grid's docked HeaderContainer.
117185         var me = this,
117186             ownerHeaderCt = me.getOwnerHeaderCt();
117187
117188         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
117189             // Firefox doesn't check the current target in a within check.
117190             // Therefore we check the target directly and then within (ancestors)
117191             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
117192                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
117193             // if its not on the left hand edge, sort
117194             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
117195                 me.toggleSortState();
117196                 ownerHeaderCt.onHeaderClick(me, e, t);
117197             }
117198         }
117199     },
117200
117201     /**
117202      * @private
117203      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
117204      * @param {String} type Event type, eg 'click'
117205      * @param {Ext.view.Table} view TableView Component
117206      * @param {HTMLElement} cell Cell HtmlElement the event took place within
117207      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
117208      * @param {Number} cellIndex Cell index within the row
117209      * @param {Ext.EventObject} e Original event
117210      */
117211     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
117212         return this.fireEvent.apply(this, arguments);
117213     },
117214
117215     toggleSortState: function() {
117216         var me = this,
117217             idx,
117218             nextIdx;
117219
117220         if (me.sortable) {
117221             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
117222
117223             nextIdx = (idx + 1) % me.possibleSortStates.length;
117224             me.setSortState(me.possibleSortStates[nextIdx]);
117225         }
117226     },
117227
117228     doSort: function(state) {
117229         var ds = this.up('tablepanel').store;
117230         ds.sort({
117231             property: this.getSortParam(),
117232             direction: state
117233         });
117234     },
117235
117236     /**
117237      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
117238      * need to be overriden in most cases.
117239      * @return {String}
117240      */
117241     getSortParam: function() {
117242         return this.dataIndex;
117243     },
117244
117245     //setSortState: function(state, updateUI) {
117246     //setSortState: function(state, doSort) {
117247     setSortState: function(state, skipClear, initial) {
117248         var me = this,
117249             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
117250             ascCls = colSortClsPrefix + 'ASC',
117251             descCls = colSortClsPrefix + 'DESC',
117252             nullCls = colSortClsPrefix + 'null',
117253             ownerHeaderCt = me.getOwnerHeaderCt(),
117254             oldSortState = me.sortState;
117255
117256         if (oldSortState !== state && me.getSortParam()) {
117257             me.addCls(colSortClsPrefix + state);
117258             // don't trigger a sort on the first time, we just want to update the UI
117259             if (state && !initial) {
117260                 me.doSort(state);
117261             }
117262             switch (state) {
117263                 case 'DESC':
117264                     me.removeCls([ascCls, nullCls]);
117265                     break;
117266                 case 'ASC':
117267                     me.removeCls([descCls, nullCls]);
117268                     break;
117269                 case null:
117270                     me.removeCls([ascCls, descCls]);
117271                     break;
117272             }
117273             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
117274                 ownerHeaderCt.clearOtherSortStates(me);
117275             }
117276             me.sortState = state;
117277             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
117278         }
117279     },
117280
117281     hide: function() {
117282         var me = this,
117283             items,
117284             len, i,
117285             lb,
117286             newWidth = 0,
117287             ownerHeaderCt = me.getOwnerHeaderCt();
117288
117289         // Hiding means setting to zero width, so cache the width
117290         me.oldWidth = me.getWidth();
117291
117292         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
117293         if (me.isGroupHeader) {
117294             items = me.items.items;
117295             me.callParent(arguments);
117296             ownerHeaderCt.onHeaderHide(me);
117297             for (i = 0, len = items.length; i < len; i++) {
117298                 items[i].hidden = true;
117299                 ownerHeaderCt.onHeaderHide(items[i], true);
117300             }
117301             return;
117302         }
117303
117304         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117305         lb = me.ownerCt.componentLayout.layoutBusy;
117306         me.ownerCt.componentLayout.layoutBusy = true;
117307         me.callParent(arguments);
117308         me.ownerCt.componentLayout.layoutBusy = lb;
117309
117310         // Notify owning HeaderContainer
117311         ownerHeaderCt.onHeaderHide(me);
117312
117313         if (me.ownerCt.isGroupHeader) {
117314             // If we've just hidden the last header in a group, then hide the group
117315             items = me.ownerCt.query('>:not([hidden])');
117316             if (!items.length) {
117317                 me.ownerCt.hide();
117318             }
117319             // Size the group down to accommodate fewer sub headers
117320             else {
117321                 for (i = 0, len = items.length; i < len; i++) {
117322                     newWidth += items[i].getWidth();
117323                 }
117324                 me.ownerCt.minWidth = newWidth;
117325                 me.ownerCt.setWidth(newWidth);
117326             }
117327         }
117328     },
117329
117330     show: function() {
117331         var me = this,
117332             ownerCt = me.ownerCt,
117333             ownerCtCompLayout = ownerCt.componentLayout,
117334             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
117335             ownerCtLayout = ownerCt.layout,
117336             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
117337             items,
117338             len, i,
117339             item,
117340             newWidth = 0;
117341
117342         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117343
117344         // Suspend our owner's layouts (both component and container):
117345         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
117346
117347         me.callParent(arguments);
117348
117349         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
117350         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
117351
117352         // If a sub header, ensure that the group header is visible
117353         if (me.isSubHeader) {
117354             if (!ownerCt.isVisible()) {
117355                 ownerCt.show();
117356             }
117357         }
117358
117359         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
117360         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
117361             items = me.query('>*');
117362             for (i = 0, len = items.length; i < len; i++) {
117363                 item = items[i];
117364                 item.preventLayout = true;
117365                 item.show();
117366                 newWidth += item.getWidth();
117367                 delete item.preventLayout;
117368             }
117369             me.setWidth(newWidth);
117370         }
117371
117372         // Resize the owning group to accommodate
117373         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
117374             items = ownerCt.query('>:not([hidden])');
117375             for (i = 0, len = items.length; i < len; i++) {
117376                 newWidth += items[i].getWidth();
117377             }
117378             ownerCt.minWidth = newWidth;
117379             ownerCt.setWidth(newWidth);
117380         }
117381
117382         // Notify owning HeaderContainer
117383         ownerCt = me.getOwnerHeaderCt();
117384         if (ownerCt) {
117385             ownerCt.onHeaderShow(me, me.preventLayout);
117386         }
117387     },
117388
117389     getDesiredWidth: function() {
117390         var me = this;
117391         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
117392             // headers always have either a width or a flex
117393             // because HeaderContainer sets a defaults width
117394             // therefore we can ignore the natural width
117395             // we use the componentLayout's tracked width so that
117396             // we can calculate the desired width when rendered
117397             // but not visible because its being obscured by a layout
117398             return me.componentLayout.lastComponentSize.width;
117399         // Flexed but yet to be rendered this could be the case
117400         // where a HeaderContainer and Headers are simply used as data
117401         // structures and not rendered.
117402         }
117403         else if (me.flex) {
117404             // this is going to be wrong, the defaultWidth
117405             return me.width;
117406         }
117407         else {
117408             return me.width;
117409         }
117410     },
117411
117412     getCellSelector: function() {
117413         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
117414     },
117415
117416     getCellInnerSelector: function() {
117417         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
117418     },
117419
117420     isOnLeftEdge: function(e) {
117421         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
117422     },
117423
117424     isOnRightEdge: function(e) {
117425         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
117426     }
117427
117428     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
117429     // when the editing plugin is injected
117430
117431     /**
117432      * @method getEditor
117433      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
117434      * associated with the Header the method will return false. If the field has not been instantiated it will be
117435      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
117436      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
117437      * @param {Object} defaultField An object representing a default field to be created
117438      * @return {Ext.form.field.Field} field
117439      */
117440     /**
117441      * @method setEditor
117442      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
117443      * been enabled on the grid.
117444      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
117445      * assumed.
117446      */
117447 });
117448
117449 /**
117450  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
117451  * an automatic row numbering column.
117452  * 
117453  * Usage:
117454  *
117455  *     columns: [
117456  *         {xtype: 'rownumberer'},
117457  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
117458  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
117459  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
117460  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
117461  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
117462  *     ]
117463  *
117464  */
117465 Ext.define('Ext.grid.RowNumberer', {
117466     extend: 'Ext.grid.column.Column',
117467     alias: 'widget.rownumberer',
117468
117469     /**
117470      * @cfg {String} text
117471      * Any valid text or HTML fragment to display in the header cell for the row number column.
117472      */
117473     text: "&#160",
117474
117475     /**
117476      * @cfg {Number} width
117477      * The default width in pixels of the row number column.
117478      */
117479     width: 23,
117480
117481     /**
117482      * @cfg {Boolean} sortable
117483      * True if the row number column is sortable.
117484      * @hide
117485      */
117486     sortable: false,
117487
117488     align: 'right',
117489
117490     constructor : function(config){
117491         this.callParent(arguments);
117492         if (this.rowspan) {
117493             this.renderer = Ext.Function.bind(this.renderer, this);
117494         }
117495     },
117496
117497     // private
117498     resizable: false,
117499     hideable: false,
117500     menuDisabled: true,
117501     dataIndex: '',
117502     cls: Ext.baseCSSPrefix + 'row-numberer',
117503     rowspan: undefined,
117504
117505     // private
117506     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
117507         if (this.rowspan){
117508             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
117509         }
117510
117511         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
117512         return store.indexOfTotal(record) + 1;
117513     }
117514 });
117515
117516 /**
117517  * @class Ext.view.DropZone
117518  * @extends Ext.dd.DropZone
117519  * @private
117520  */
117521 Ext.define('Ext.view.DropZone', {
117522     extend: 'Ext.dd.DropZone',
117523
117524     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117525     indicatorCls: 'x-grid-drop-indicator',
117526
117527     constructor: function(config) {
117528         var me = this;
117529         Ext.apply(me, config);
117530
117531         // Create a ddGroup unless one has been configured.
117532         // User configuration of ddGroups allows users to specify which
117533         // DD instances can interact with each other. Using one
117534         // based on the id of the View would isolate it and mean it can only
117535         // interact with a DragZone on the same View also using a generated ID.
117536         if (!me.ddGroup) {
117537             me.ddGroup = 'view-dd-zone-' + me.view.id;
117538         }
117539
117540         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
117541         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
117542         // same element, so a DragZone on this same View must use the View's parent element as its element.
117543         me.callParent([me.view.el]);
117544     },
117545
117546 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
117547 //  its data does not become corrupted by processing mouse events.
117548     fireViewEvent: function() {
117549         var me = this,
117550             result;
117551
117552         me.lock();
117553         result = me.view.fireEvent.apply(me.view, arguments);
117554         me.unlock();
117555         return result;
117556     },
117557
117558     getTargetFromEvent : function(e) {
117559         var node = e.getTarget(this.view.getItemSelector()),
117560             mouseY, nodeList, testNode, i, len, box;
117561
117562 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
117563 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
117564         if (!node) {
117565             mouseY = e.getPageY();
117566             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
117567                 testNode = nodeList[i];
117568                 box = Ext.fly(testNode).getBox();
117569                 if (mouseY <= box.bottom) {
117570                     return testNode;
117571                 }
117572             }
117573         }
117574         return node;
117575     },
117576
117577     getIndicator: function() {
117578         var me = this;
117579
117580         if (!me.indicator) {
117581             me.indicator = Ext.createWidget('component', {
117582                 html: me.indicatorHtml,
117583                 cls: me.indicatorCls,
117584                 ownerCt: me.view,
117585                 floating: true,
117586                 shadow: false
117587             });
117588         }
117589         return me.indicator;
117590     },
117591
117592     getPosition: function(e, node) {
117593         var y      = e.getXY()[1],
117594             region = Ext.fly(node).getRegion(),
117595             pos;
117596
117597         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
117598             pos = "before";
117599         } else {
117600             pos = "after";
117601         }
117602         return pos;
117603     },
117604
117605     /**
117606      * @private Determines whether the record at the specified offset from the passed record
117607      * is in the drag payload.
117608      * @param records
117609      * @param record
117610      * @param offset
117611      * @returns {Boolean} True if the targeted record is in the drag payload
117612      */
117613     containsRecordAtOffset: function(records, record, offset) {
117614         if (!record) {
117615             return false;
117616         }
117617         var view = this.view,
117618             recordIndex = view.indexOf(record),
117619             nodeBefore = view.getNode(recordIndex + offset),
117620             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
117621
117622         return recordBefore && Ext.Array.contains(records, recordBefore);
117623     },
117624
117625     positionIndicator: function(node, data, e) {
117626         var me = this,
117627             view = me.view,
117628             pos = me.getPosition(e, node),
117629             overRecord = view.getRecord(node),
117630             draggingRecords = data.records,
117631             indicator, indicatorY;
117632
117633         if (!Ext.Array.contains(draggingRecords, overRecord) && (
117634             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
117635             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
117636         )) {
117637             me.valid = true;
117638
117639             if (me.overRecord != overRecord || me.currentPosition != pos) {
117640
117641                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
117642                 if (pos == 'after') {
117643                     indicatorY += Ext.fly(node).getHeight();
117644                 }
117645                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
117646
117647                 // Cache the overRecord and the 'before' or 'after' indicator.
117648                 me.overRecord = overRecord;
117649                 me.currentPosition = pos;
117650             }
117651         } else {
117652             me.invalidateDrop();
117653         }
117654     },
117655
117656     invalidateDrop: function() {
117657         if (this.valid) {
117658             this.valid = false;
117659             this.getIndicator().hide();
117660         }
117661     },
117662
117663     // The mouse is over a View node
117664     onNodeOver: function(node, dragZone, e, data) {
117665         var me = this;
117666
117667         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
117668             me.positionIndicator(node, data, e);
117669         }
117670         return me.valid ? me.dropAllowed : me.dropNotAllowed;
117671     },
117672
117673     // Moved out of the DropZone without dropping.
117674     // Remove drop position indicator
117675     notifyOut: function(node, dragZone, e, data) {
117676         var me = this;
117677
117678         me.callParent(arguments);
117679         delete me.overRecord;
117680         delete me.currentPosition;
117681         if (me.indicator) {
117682             me.indicator.hide();
117683         }
117684     },
117685
117686     // The mouse is past the end of all nodes (or there are no nodes)
117687     onContainerOver : function(dd, e, data) {
117688         var me = this,
117689             view = me.view,
117690             count = view.store.getCount();
117691
117692         // There are records, so position after the last one
117693         if (count) {
117694             me.positionIndicator(view.getNode(count - 1), data, e);
117695         }
117696
117697         // No records, position the indicator at the top
117698         else {
117699             delete me.overRecord;
117700             delete me.currentPosition;
117701             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
117702             me.valid = true;
117703         }
117704         return me.dropAllowed;
117705     },
117706
117707     onContainerDrop : function(dd, e, data) {
117708         return this.onNodeDrop(dd, null, e, data);
117709     },
117710
117711     onNodeDrop: function(node, dragZone, e, data) {
117712         var me = this,
117713             dropped = false,
117714
117715             // Create a closure to perform the operation which the event handler may use.
117716             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
117717             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
117718             // and complete the drop gesture at some point in the future by calling this function.
117719             processDrop = function () {
117720                 me.invalidateDrop();
117721                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
117722                 dropped = true;
117723                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
117724             },
117725             performOperation = false;
117726
117727         if (me.valid) {
117728             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
117729             if (performOperation !== false) {
117730                 // If the processDrop function was called in the event handler, do not do it again.
117731                 if (!dropped) {
117732                     processDrop();
117733                 }
117734             }
117735         }
117736         return performOperation;
117737     },
117738     
117739     destroy: function(){
117740         Ext.destroy(this.indicator);
117741         delete this.indicator;
117742         this.callParent();
117743     }
117744 });
117745
117746 Ext.define('Ext.grid.ViewDropZone', {
117747     extend: 'Ext.view.DropZone',
117748
117749     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117750     indicatorCls: 'x-grid-drop-indicator',
117751
117752     handleNodeDrop : function(data, record, position) {
117753         var view = this.view,
117754             store = view.getStore(),
117755             index, records, i, len;
117756
117757         // If the copy flag is set, create a copy of the Models with the same IDs
117758         if (data.copy) {
117759             records = data.records;
117760             data.records = [];
117761             for (i = 0, len = records.length; i < len; i++) {
117762                 data.records.push(records[i].copy(records[i].getId()));
117763             }
117764         } else {
117765             /*
117766              * Remove from the source store. We do this regardless of whether the store
117767              * is the same bacsue the store currently doesn't handle moving records
117768              * within the store. In the future it should be possible to do this.
117769              * Here was pass the isMove parameter if we're moving to the same view.
117770              */
117771             data.view.store.remove(data.records, data.view === view);
117772         }
117773
117774         index = store.indexOf(record);
117775
117776         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
117777         if (position !== 'before') {
117778             index++;
117779         }
117780         store.insert(index, data.records);
117781         view.getSelectionModel().select(data.records);
117782     }
117783 });
117784 /**
117785  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
117786  * handler for each icon.
117787  *
117788  *     @example
117789  *     Ext.create('Ext.data.Store', {
117790  *         storeId:'employeeStore',
117791  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
117792  *         data:[
117793  *             {firstname:"Michael", lastname:"Scott"},
117794  *             {firstname:"Dwight", lastname:"Schrute"},
117795  *             {firstname:"Jim", lastname:"Halpert"},
117796  *             {firstname:"Kevin", lastname:"Malone"},
117797  *             {firstname:"Angela", lastname:"Martin"}
117798  *         ]
117799  *     });
117800  *
117801  *     Ext.create('Ext.grid.Panel', {
117802  *         title: 'Action Column Demo',
117803  *         store: Ext.data.StoreManager.lookup('employeeStore'),
117804  *         columns: [
117805  *             {text: 'First Name',  dataIndex:'firstname'},
117806  *             {text: 'Last Name',  dataIndex:'lastname'},
117807  *             {
117808  *                 xtype:'actioncolumn',
117809  *                 width:50,
117810  *                 items: [{
117811  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
117812  *                     tooltip: 'Edit',
117813  *                     handler: function(grid, rowIndex, colIndex) {
117814  *                         var rec = grid.getStore().getAt(rowIndex);
117815  *                         alert("Edit " + rec.get('firstname'));
117816  *                     }
117817  *                 },{
117818  *                     icon: 'extjs/examples/restful/images/delete.png',
117819  *                     tooltip: 'Delete',
117820  *                     handler: function(grid, rowIndex, colIndex) {
117821  *                         var rec = grid.getStore().getAt(rowIndex);
117822  *                         alert("Terminate " + rec.get('firstname'));
117823  *                     }
117824  *                 }]
117825  *             }
117826  *         ],
117827  *         width: 250,
117828  *         renderTo: Ext.getBody()
117829  *     });
117830  *
117831  * The action column can be at any index in the columns array, and a grid can have any number of
117832  * action columns.
117833  */
117834 Ext.define('Ext.grid.column.Action', {
117835     extend: 'Ext.grid.column.Column',
117836     alias: ['widget.actioncolumn'],
117837     alternateClassName: 'Ext.grid.ActionColumn',
117838
117839     /**
117840      * @cfg {String} icon
117841      * The URL of an image to display as the clickable element in the column. Defaults to
117842      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
117843      */
117844     /**
117845      * @cfg {String} iconCls
117846      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
117847      * a `{@link #getClass}` function.
117848      */
117849     /**
117850      * @cfg {Function} handler
117851      * A function called when the icon is clicked.
117852      * @cfg {Ext.view.Table} handler.view The owning TableView.
117853      * @cfg {Number} handler.rowIndex The row index clicked on.
117854      * @cfg {Number} handler.colIndex The column index clicked on.
117855      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
117856      * @cfg {Event} handler.e The click event.
117857      */
117858     /**
117859      * @cfg {Object} scope
117860      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
117861      * Defaults to this Column.
117862      */
117863     /**
117864      * @cfg {String} tooltip
117865      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
117866      * have been initialized.
117867      */
117868     /* @cfg {Boolean} disabled
117869      * If true, the action will not respond to click events, and will be displayed semi-opaque.
117870      */
117871     /**
117872      * @cfg {Boolean} [stopSelection=true]
117873      * Prevent grid _row_ selection upon mousedown.
117874      */
117875     /**
117876      * @cfg {Function} getClass
117877      * A function which returns the CSS class to apply to the icon image.
117878      *
117879      * @cfg {Object} getClass.v The value of the column's configured field (if any).
117880      *
117881      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
117882      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
117883      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
117884      * element *within* the table cell (e.g. 'style="color:red;"').
117885      *
117886      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
117887      *
117888      * @cfg {Number} getClass.rowIndex The row index..
117889      *
117890      * @cfg {Number} getClass.colIndex The column index.
117891      *
117892      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
117893      */
117894     /**
117895      * @cfg {Object[]} items
117896      * An Array which may contain multiple icon definitions, each element of which may contain:
117897      *
117898      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
117899      *
117900      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
117901      * configure the item with a `getClass` function.
117902      *
117903      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
117904      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
117905      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
117906      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
117907      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
117908      * container element _within_ the table cell (e.g. 'style="color:red;"').
117909      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
117910      * @cfg {Number} items.getClass.rowIndex The row index..
117911      * @cfg {Number} items.getClass.colIndex The column index.
117912      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
117913      *
117914      * @cfg {Function} items.handler A function called when the icon is clicked.
117915      *
117916      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
117917      * are executed. Fallback defaults are this Column's configured scope, then this Column.
117918      *
117919      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
117920      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
117921      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
117922      */
117923     /**
117924      * @property {Array} items
117925      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
117926      * an `enable` and `disable` method added which will enable and disable the associated action, and
117927      * update the displayed icon accordingly.
117928      */
117929     header: '&#160;',
117930
117931     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
117932
117933     /**
117934      * @cfg {String} altText
117935      * The alt text to use for the image element.
117936      */
117937     altText: '',
117938
117939     sortable: false,
117940
117941     constructor: function(config) {
117942         var me = this,
117943             cfg = Ext.apply({}, config),
117944             items = cfg.items || [me],
117945             l = items.length,
117946             i,
117947             item;
117948
117949         // This is a Container. Delete the items config to be reinstated after construction.
117950         delete cfg.items;
117951         me.callParent([cfg]);
117952
117953         // Items is an array property of ActionColumns
117954         me.items = items;
117955
117956 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
117957 //      class name x-action-col-{n}
117958         me.renderer = function(v, meta) {
117959 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
117960             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
117961
117962             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
117963             for (i = 0; i < l; i++) {
117964                 item = items[i];
117965                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
117966                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
117967                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
117968                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
117969                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
117970                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
117971             }
117972             return v;
117973         };
117974     },
117975
117976     /**
117977      * Enables this ActionColumn's action at the specified index.
117978      */
117979     enableAction: function(index) {
117980         var me = this;
117981
117982         if (!index) {
117983             index = 0;
117984         } else if (!Ext.isNumber(index)) {
117985             index = Ext.Array.indexOf(me.items, index);
117986         }
117987         me.items[index].disabled = false;
117988         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
117989     },
117990
117991     /**
117992      * Disables this ActionColumn's action at the specified index.
117993      */
117994     disableAction: function(index) {
117995         var me = this;
117996
117997         if (!index) {
117998             index = 0;
117999         } else if (!Ext.isNumber(index)) {
118000             index = Ext.Array.indexOf(me.items, index);
118001         }
118002         me.items[index].disabled = true;
118003         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
118004     },
118005
118006     destroy: function() {
118007         delete this.items;
118008         delete this.renderer;
118009         return this.callParent(arguments);
118010     },
118011
118012     /**
118013      * @private
118014      * Process and refire events routed from the GridView's processEvent method.
118015      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
118016      * Returns the event handler's status to allow canceling of GridView's bubbling process.
118017      */
118018     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
118019         var me = this,
118020             match = e.getTarget().className.match(me.actionIdRe),
118021             item, fn;
118022             
118023         if (match) {
118024             item = me.items[parseInt(match[1], 10)];
118025             if (item) {
118026                 if (type == 'click') {
118027                     fn = item.handler || me.handler;
118028                     if (fn && !item.disabled) {
118029                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
118030                     }
118031                 } else if (type == 'mousedown' && item.stopSelection !== false) {
118032                     return false;
118033                 }
118034             }
118035         }
118036         return me.callParent(arguments);
118037     },
118038
118039     cascade: function(fn, scope) {
118040         fn.call(scope||this, this);
118041     },
118042
118043     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
118044     getRefItems: function() {
118045         return [];
118046     }
118047 });
118048 /**
118049  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
118050  * config option of {@link Ext.grid.column.Column} for more details.
118051  *
118052  *     @example
118053  *     Ext.create('Ext.data.Store', {
118054  *        storeId:'sampleStore',
118055  *        fields:[
118056  *            {name: 'framework', type: 'string'},
118057  *            {name: 'rocks', type: 'boolean'}
118058  *        ],
118059  *        data:{'items':[
118060  *            { 'framework': "Ext JS 4",     'rocks': true  },
118061  *            { 'framework': "Sencha Touch", 'rocks': true  },
118062  *            { 'framework': "Ext GWT",      'rocks': true  }, 
118063  *            { 'framework': "Other Guys",   'rocks': false } 
118064  *        ]},
118065  *        proxy: {
118066  *            type: 'memory',
118067  *            reader: {
118068  *                type: 'json',
118069  *                root: 'items'
118070  *            }
118071  *        }
118072  *     });
118073  *     
118074  *     Ext.create('Ext.grid.Panel', {
118075  *         title: 'Boolean Column Demo',
118076  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118077  *         columns: [
118078  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
118079  *             {
118080  *                 xtype: 'booleancolumn', 
118081  *                 text: 'Rocks',
118082  *                 trueText: 'Yes',
118083  *                 falseText: 'No', 
118084  *                 dataIndex: 'rocks'
118085  *             }
118086  *         ],
118087  *         height: 200,
118088  *         width: 400,
118089  *         renderTo: Ext.getBody()
118090  *     });
118091  */
118092 Ext.define('Ext.grid.column.Boolean', {
118093     extend: 'Ext.grid.column.Column',
118094     alias: ['widget.booleancolumn'],
118095     alternateClassName: 'Ext.grid.BooleanColumn',
118096
118097     /**
118098      * @cfg {String} trueText
118099      * The string returned by the renderer when the column value is not falsey.
118100      */
118101     trueText: 'true',
118102
118103     /**
118104      * @cfg {String} falseText
118105      * The string returned by the renderer when the column value is falsey (but not undefined).
118106      */
118107     falseText: 'false',
118108
118109     /**
118110      * @cfg {String} undefinedText
118111      * The string returned by the renderer when the column value is undefined.
118112      */
118113     undefinedText: '&#160;',
118114
118115     constructor: function(cfg){
118116         this.callParent(arguments);
118117         var trueText      = this.trueText,
118118             falseText     = this.falseText,
118119             undefinedText = this.undefinedText;
118120
118121         this.renderer = function(value){
118122             if(value === undefined){
118123                 return undefinedText;
118124             }
118125             if(!value || value === 'false'){
118126                 return falseText;
118127             }
118128             return trueText;
118129         };
118130     }
118131 });
118132 /**
118133  * A Column definition class which renders a passed date according to the default locale, or a configured
118134  * {@link #format}.
118135  *
118136  *     @example
118137  *     Ext.create('Ext.data.Store', {
118138  *         storeId:'sampleStore',
118139  *         fields:[
118140  *             { name: 'symbol', type: 'string' },
118141  *             { name: 'date',   type: 'date' },
118142  *             { name: 'change', type: 'number' },
118143  *             { name: 'volume', type: 'number' },
118144  *             { name: 'topday', type: 'date' }                        
118145  *         ],
118146  *         data:[
118147  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
118148  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
118149  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
118150  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
118151  *         ]
118152  *     });
118153  *     
118154  *     Ext.create('Ext.grid.Panel', {
118155  *         title: 'Date Column Demo',
118156  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118157  *         columns: [
118158  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
118159  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
118160  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118161  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
118162  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
118163  *         ],
118164  *         height: 200,
118165  *         width: 450,
118166  *         renderTo: Ext.getBody()
118167  *     });
118168  */
118169 Ext.define('Ext.grid.column.Date', {
118170     extend: 'Ext.grid.column.Column',
118171     alias: ['widget.datecolumn'],
118172     requires: ['Ext.Date'],
118173     alternateClassName: 'Ext.grid.DateColumn',
118174
118175     /**
118176      * @cfg {String} format
118177      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
118178      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
118179      * in a locale file.
118180      */
118181
118182     initComponent: function(){
118183         var me = this;
118184         
118185         me.callParent(arguments);
118186         if (!me.format) {
118187             me.format = Ext.Date.defaultFormat;
118188         }
118189         me.renderer = Ext.util.Format.dateRenderer(me.format);
118190     }
118191 });
118192 /**
118193  * A Column definition class which renders a numeric data field according to a {@link #format} string.
118194  *
118195  *     @example
118196  *     Ext.create('Ext.data.Store', {
118197  *        storeId:'sampleStore',
118198  *        fields:[
118199  *            { name: 'symbol', type: 'string' },
118200  *            { name: 'price',  type: 'number' },
118201  *            { name: 'change', type: 'number' },
118202  *            { name: 'volume', type: 'number' },            
118203  *        ],
118204  *        data:[
118205  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
118206  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
118207  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
118208  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
118209  *        ]
118210  *     });
118211  *     
118212  *     Ext.create('Ext.grid.Panel', {
118213  *         title: 'Number Column Demo',
118214  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118215  *         columns: [
118216  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
118217  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
118218  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118219  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
118220  *         ],
118221  *         height: 200,
118222  *         width: 400,
118223  *         renderTo: Ext.getBody()
118224  *     });
118225  */
118226 Ext.define('Ext.grid.column.Number', {
118227     extend: 'Ext.grid.column.Column',
118228     alias: ['widget.numbercolumn'],
118229     requires: ['Ext.util.Format'],
118230     alternateClassName: 'Ext.grid.NumberColumn',
118231
118232     /**
118233      * @cfg {String} format
118234      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
118235      */
118236     format : '0,000.00',
118237
118238     constructor: function(cfg) {
118239         this.callParent(arguments);
118240         this.renderer = Ext.util.Format.numberRenderer(this.format);
118241     }
118242 });
118243 /**
118244  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
118245  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
118246  * {@link Ext.XTemplate XTemplate}.
118247  * 
118248  *     @example
118249  *     Ext.create('Ext.data.Store', {
118250  *         storeId:'employeeStore',
118251  *         fields:['firstname', 'lastname', 'senority', 'department'],
118252  *         groupField: 'department',
118253  *         data:[
118254  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
118255  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
118256  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
118257  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
118258  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
118259  *         ]
118260  *     });
118261  *     
118262  *     Ext.create('Ext.grid.Panel', {
118263  *         title: 'Column Template Demo',
118264  *         store: Ext.data.StoreManager.lookup('employeeStore'),
118265  *         columns: [
118266  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
118267  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
118268  *         ],
118269  *         height: 200,
118270  *         width: 300,
118271  *         renderTo: Ext.getBody()
118272  *     });
118273  */
118274 Ext.define('Ext.grid.column.Template', {
118275     extend: 'Ext.grid.column.Column',
118276     alias: ['widget.templatecolumn'],
118277     requires: ['Ext.XTemplate'],
118278     alternateClassName: 'Ext.grid.TemplateColumn',
118279
118280     /**
118281      * @cfg {String/Ext.XTemplate} tpl
118282      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
118283      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
118284      * column's rendered value.
118285      */
118286
118287     constructor: function(cfg){
118288         var me = this,
118289             tpl;
118290             
118291         me.callParent(arguments);
118292         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
118293
118294         me.renderer = function(value, p, record) {
118295             var data = Ext.apply({}, record.data, record.getAssociatedData());
118296             return tpl.apply(data);
118297         };
118298     }
118299 });
118300
118301 /**
118302  * @class Ext.grid.feature.Feature
118303  * @extends Ext.util.Observable
118304  * 
118305  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
118306  * hooks that allows the developer to inject additional functionality at certain points throughout the 
118307  * grid creation cycle. This class provides the base template methods that are available to the developer,
118308  * it should be extended.
118309  * 
118310  * There are several built in features that extend this class, for example:
118311  *
118312  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
118313  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
118314  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
118315  * 
118316  * ## Using Features
118317  * A feature is added to the grid by specifying it an array of features in the configuration:
118318  * 
118319  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
118320  *     Ext.create('Ext.grid.Panel', {
118321  *         // other options
118322  *         features: [groupingFeature]
118323  *     });
118324  * 
118325  * @abstract
118326  */
118327 Ext.define('Ext.grid.feature.Feature', {
118328     extend: 'Ext.util.Observable',
118329     alias: 'feature.feature',
118330     
118331     isFeature: true,
118332     disabled: false,
118333     
118334     /**
118335      * @property {Boolean}
118336      * Most features will expose additional events, some may not and will
118337      * need to change this to false.
118338      */
118339     hasFeatureEvent: true,
118340     
118341     /**
118342      * @property {String}
118343      * Prefix to use when firing events on the view.
118344      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
118345      */
118346     eventPrefix: null,
118347     
118348     /**
118349      * @property {String}
118350      * Selector used to determine when to fire the event with the eventPrefix.
118351      */
118352     eventSelector: null,
118353     
118354     /**
118355      * @property {Ext.view.Table}
118356      * Reference to the TableView.
118357      */
118358     view: null,
118359     
118360     /**
118361      * @property {Ext.grid.Panel}
118362      * Reference to the grid panel
118363      */
118364     grid: null,
118365     
118366     /**
118367      * Most features will not modify the data returned to the view.
118368      * This is limited to one feature that manipulates the data per grid view.
118369      */
118370     collectData: false,
118371         
118372     getFeatureTpl: function() {
118373         return '';
118374     },
118375     
118376     /**
118377      * Abstract method to be overriden when a feature should add additional
118378      * arguments to its event signature. By default the event will fire:
118379      * - view - The underlying Ext.view.Table
118380      * - featureTarget - The matched element by the defined {@link #eventSelector}
118381      *
118382      * The method must also return the eventName as the first index of the array
118383      * to be passed to fireEvent.
118384      * @template
118385      */
118386     getFireEventArgs: function(eventName, view, featureTarget, e) {
118387         return [eventName, view, featureTarget, e];
118388     },
118389     
118390     /**
118391      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
118392      * @template
118393      */
118394     attachEvents: function() {
118395         
118396     },
118397     
118398     getFragmentTpl: function() {
118399         return;
118400     },
118401     
118402     /**
118403      * Allows a feature to mutate the metaRowTpl.
118404      * The array received as a single argument can be manipulated to add things
118405      * on the end/begining of a particular row.
118406      * @template
118407      */
118408     mutateMetaRowTpl: function(metaRowTplArray) {
118409         
118410     },
118411     
118412     /**
118413      * Allows a feature to inject member methods into the metaRowTpl. This is
118414      * important for embedding functionality which will become part of the proper
118415      * row tpl.
118416      * @template
118417      */
118418     getMetaRowTplFragments: function() {
118419         return {};
118420     },
118421
118422     getTableFragments: function() {
118423         return {};
118424     },
118425     
118426     /**
118427      * Provide additional data to the prepareData call within the grid view.
118428      * @param {Object} data The data for this particular record.
118429      * @param {Number} idx The row index for this record.
118430      * @param {Ext.data.Model} record The record instance
118431      * @param {Object} orig The original result from the prepareData call to massage.
118432      * @template
118433      */
118434     getAdditionalData: function(data, idx, record, orig) {
118435         return {};
118436     },
118437     
118438     /**
118439      * Enable a feature
118440      */
118441     enable: function() {
118442         this.disabled = false;
118443     },
118444     
118445     /**
118446      * Disable a feature
118447      */
118448     disable: function() {
118449         this.disabled = true;
118450     }
118451     
118452 });
118453 /**
118454  * @class Ext.grid.feature.AbstractSummary
118455  * @extends Ext.grid.feature.Feature
118456  * A small abstract class that contains the shared behaviour for any summary
118457  * calculations to be used in the grid.
118458  */
118459 Ext.define('Ext.grid.feature.AbstractSummary', {
118460     
118461     /* Begin Definitions */
118462    
118463     extend: 'Ext.grid.feature.Feature',
118464     
118465     alias: 'feature.abstractsummary',
118466    
118467     /* End Definitions */
118468    
118469    /**
118470     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
118471     */
118472     showSummaryRow: true,
118473     
118474     // @private
118475     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
118476     
118477     /**
118478      * Toggle whether or not to show the summary row.
118479      * @param {Boolean} visible True to show the summary row
118480      */
118481     toggleSummaryRow: function(visible){
118482         this.showSummaryRow = !!visible;
118483     },
118484     
118485     /**
118486      * Gets any fragments to be used in the tpl
118487      * @private
118488      * @return {Object} The fragments
118489      */
118490     getSummaryFragments: function(){
118491         var fragments = {};
118492         if (this.showSummaryRow) {
118493             Ext.apply(fragments, {
118494                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
118495             });
118496         }
118497         return fragments;
118498     },
118499     
118500     /**
118501      * Prints a summary row
118502      * @private
118503      * @param {Object} index The index in the template
118504      * @return {String} The value of the summary row
118505      */
118506     printSummaryRow: function(index){
118507         var inner = this.view.getTableChunker().metaRowTpl.join(''),
118508             prefix = Ext.baseCSSPrefix;
118509         
118510         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
118511         inner = inner.replace('{{id}}', '{gridSummaryValue}');
118512         inner = inner.replace(this.nestedIdRe, '{id$1}');  
118513         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
118514         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
118515         inner = Ext.create('Ext.XTemplate', inner, {
118516             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
118517         });
118518         
118519         return inner.applyTemplate({
118520             columns: this.getPrintData(index)
118521         });
118522     },
118523     
118524     /**
118525      * Gets the value for the column from the attached data.
118526      * @param {Ext.grid.column.Column} column The header
118527      * @param {Object} data The current data
118528      * @return {String} The value to be rendered
118529      */
118530     getColumnValue: function(column, summaryData){
118531         var comp     = Ext.getCmp(column.id),
118532             value    = summaryData[column.id],
118533             renderer = comp.summaryRenderer;
118534
118535         if (renderer) {
118536             value = renderer.call(
118537                 comp.scope || this,
118538                 value,
118539                 summaryData,
118540                 column.dataIndex
118541             );
118542         }
118543         return value;
118544     },
118545     
118546     /**
118547      * Get the summary data for a field.
118548      * @private
118549      * @param {Ext.data.Store} store The store to get the data from
118550      * @param {String/Function} type The type of aggregation. If a function is specified it will
118551      * be passed to the stores aggregate function.
118552      * @param {String} field The field to aggregate on
118553      * @param {Boolean} group True to aggregate in grouped mode 
118554      * @return {Number/String/Object} See the return type for the store functions.
118555      */
118556     getSummary: function(store, type, field, group){
118557         if (type) {
118558             if (Ext.isFunction(type)) {
118559                 return store.aggregate(type, null, group);
118560             }
118561             
118562             switch (type) {
118563                 case 'count':
118564                     return store.count(group);
118565                 case 'min':
118566                     return store.min(field, group);
118567                 case 'max':
118568                     return store.max(field, group);
118569                 case 'sum':
118570                     return store.sum(field, group);
118571                 case 'average':
118572                     return store.average(field, group);
118573                 default:
118574                     return group ? {} : '';
118575                     
118576             }
118577         }
118578     }
118579     
118580 });
118581
118582 /**
118583  * @class Ext.grid.feature.Chunking
118584  * @extends Ext.grid.feature.Feature
118585  */
118586 Ext.define('Ext.grid.feature.Chunking', {
118587     extend: 'Ext.grid.feature.Feature',
118588     alias: 'feature.chunking',
118589     
118590     chunkSize: 20,
118591     rowHeight: Ext.isIE ? 27 : 26,
118592     visibleChunk: 0,
118593     hasFeatureEvent: false,
118594     attachEvents: function() {
118595         var grid = this.view.up('gridpanel'),
118596             scroller = grid.down('gridscroller[dock=right]');
118597         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
118598         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
118599     },
118600     
118601     onBodyScroll: function(e, t) {
118602         var view = this.view,
118603             top  = t.scrollTop,
118604             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
118605         if (nextChunk !== this.visibleChunk) {
118606         
118607             this.visibleChunk = nextChunk;
118608             view.refresh();
118609             view.el.dom.scrollTop = top;
118610             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
118611             view.el.dom.scrollTop = top;
118612         }
118613     },
118614     
118615     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
118616         var o = {
118617             fullWidth: orig.fullWidth,
118618             chunks: []
118619         },
118620         //headerCt = this.view.headerCt,
118621         //colums = headerCt.getColumnsForTpl(),
118622         recordCount = orig.rows.length,
118623         start = 0,
118624         i = 0,
118625         visibleChunk = this.visibleChunk,
118626         chunk,
118627         rows,
118628         chunkLength;
118629
118630         for (; start < recordCount; start+=this.chunkSize, i++) {
118631             if (start+this.chunkSize > recordCount) {
118632                 chunkLength = recordCount - start;
118633             } else {
118634                 chunkLength = this.chunkSize;
118635             }
118636             
118637             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
118638                 rows = orig.rows.slice(start, start+this.chunkSize);
118639             } else {
118640                 rows = [];
118641             }
118642             o.chunks.push({
118643                 rows: rows,
118644                 fullWidth: fullWidth,
118645                 chunkHeight: chunkLength * this.rowHeight
118646             });
118647         }
118648         
118649         
118650         return o;
118651     },
118652     
118653     getTableFragments: function() {
118654         return {
118655             openTableWrap: function() {
118656                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
118657             },
118658             closeTableWrap: function() {
118659                 return '</div></tpl>';
118660             }
118661         };
118662     }
118663 });
118664
118665 /**
118666  * @class Ext.grid.feature.Grouping
118667  * @extends Ext.grid.feature.Feature
118668  * 
118669  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
118670  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
118671  * underneath. The groups can also be expanded and collapsed.
118672  * 
118673  * ## Extra Events
118674  * This feature adds several extra events that will be fired on the grid to interact with the groups:
118675  *
118676  *  - {@link #groupclick}
118677  *  - {@link #groupdblclick}
118678  *  - {@link #groupcontextmenu}
118679  *  - {@link #groupexpand}
118680  *  - {@link #groupcollapse}
118681  * 
118682  * ## Menu Augmentation
118683  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
118684  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
118685  * by thew user is {@link #enableNoGroups}.
118686  * 
118687  * ## Controlling Group Text
118688  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
118689  * the default display.
118690  * 
118691  * ## Example Usage
118692  * 
118693  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
118694  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
118695  *         startCollapsed: true // start all groups collapsed
118696  *     });
118697  * 
118698  * @ftype grouping
118699  * @author Nicolas Ferrero
118700  */
118701 Ext.define('Ext.grid.feature.Grouping', {
118702     extend: 'Ext.grid.feature.Feature',
118703     alias: 'feature.grouping',
118704
118705     eventPrefix: 'group',
118706     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
118707
118708     constructor: function() {
118709         var me = this;
118710         
118711         me.collapsedState = {};
118712         me.callParent(arguments);
118713     },
118714     
118715     /**
118716      * @event groupclick
118717      * @param {Ext.view.Table} view
118718      * @param {HTMLElement} node
118719      * @param {String} group The name of the group
118720      * @param {Ext.EventObject} e
118721      */
118722
118723     /**
118724      * @event groupdblclick
118725      * @param {Ext.view.Table} view
118726      * @param {HTMLElement} node
118727      * @param {String} group The name of the group
118728      * @param {Ext.EventObject} e
118729      */
118730
118731     /**
118732      * @event groupcontextmenu
118733      * @param {Ext.view.Table} view
118734      * @param {HTMLElement} node
118735      * @param {String} group The name of the group
118736      * @param {Ext.EventObject} e
118737      */
118738
118739     /**
118740      * @event groupcollapse
118741      * @param {Ext.view.Table} view
118742      * @param {HTMLElement} node
118743      * @param {String} group The name of the group
118744      * @param {Ext.EventObject} e
118745      */
118746
118747     /**
118748      * @event groupexpand
118749      * @param {Ext.view.Table} view
118750      * @param {HTMLElement} node
118751      * @param {String} group The name of the group
118752      * @param {Ext.EventObject} e
118753      */
118754
118755     /**
118756      * @cfg {String} groupHeaderTpl
118757      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
118758      * Defaults to 'Group: {name}'
118759      */
118760     groupHeaderTpl: 'Group: {name}',
118761
118762     /**
118763      * @cfg {Number} depthToIndent
118764      * Number of pixels to indent per grouping level
118765      */
118766     depthToIndent: 17,
118767
118768     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
118769     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
118770
118771     /**
118772      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
118773      */
118774     groupByText : 'Group By This Field',
118775     /**
118776      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
118777      */
118778     showGroupsText : 'Show in Groups',
118779
118780     /**
118781      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
118782      */
118783     hideGroupedHeader : false,
118784
118785     /**
118786      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
118787      */
118788     startCollapsed : false,
118789
118790     /**
118791      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
118792      */
118793     enableGroupingMenu : true,
118794
118795     /**
118796      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
118797      */
118798     enableNoGroups : true,
118799     
118800     enable: function() {
118801         var me    = this,
118802             view  = me.view,
118803             store = view.store,
118804             groupToggleMenuItem;
118805             
118806         me.lastGroupField = me.getGroupField();
118807
118808         if (me.lastGroupIndex) {
118809             store.group(me.lastGroupIndex);
118810         }
118811         me.callParent();
118812         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118813         groupToggleMenuItem.setChecked(true, true);
118814         me.refreshIf();
118815     },
118816
118817     disable: function() {
118818         var me    = this,
118819             view  = me.view,
118820             store = view.store,
118821             remote = store.remoteGroup,
118822             groupToggleMenuItem,
118823             lastGroup;
118824             
118825         lastGroup = store.groupers.first();
118826         if (lastGroup) {
118827             me.lastGroupIndex = lastGroup.property;
118828             me.block();
118829             store.clearGrouping();
118830             me.unblock();
118831         }
118832         
118833         me.callParent();
118834         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118835         groupToggleMenuItem.setChecked(true, true);
118836         groupToggleMenuItem.setChecked(false, true);
118837         if (!remote) {
118838             view.refresh();
118839         }
118840     },
118841     
118842     refreshIf: function() {
118843         if (this.blockRefresh !== true) {
118844             this.view.refresh();
118845         }    
118846     },
118847
118848     getFeatureTpl: function(values, parent, x, xcount) {
118849         var me = this;
118850         
118851         return [
118852             '<tpl if="typeof rows !== \'undefined\'">',
118853                 // group row tpl
118854                 '<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>',
118855                 // this is the rowbody
118856                 '<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>',
118857             '</tpl>'
118858         ].join('');
118859     },
118860
118861     getFragmentTpl: function() {
118862         return {
118863             indentByDepth: this.indentByDepth,
118864             depthToIndent: this.depthToIndent
118865         };
118866     },
118867
118868     indentByDepth: function(values) {
118869         var depth = values.depth || 0;
118870         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
118871     },
118872
118873     // Containers holding these components are responsible for
118874     // destroying them, we are just deleting references.
118875     destroy: function() {
118876         var me = this;
118877         
118878         delete me.view;
118879         delete me.prunedHeader;
118880     },
118881
118882     // perhaps rename to afterViewRender
118883     attachEvents: function() {
118884         var me = this,
118885             view = me.view;
118886
118887         view.on({
118888             scope: me,
118889             groupclick: me.onGroupClick,
118890             rowfocus: me.onRowFocus
118891         });
118892         view.store.on('groupchange', me.onGroupChange, me);
118893
118894         me.pruneGroupedHeader();
118895
118896         if (me.enableGroupingMenu) {
118897             me.injectGroupingMenu();
118898         }
118899         me.lastGroupField = me.getGroupField();
118900         me.block();
118901         me.onGroupChange();
118902         me.unblock();
118903     },
118904     
118905     injectGroupingMenu: function() {
118906         var me       = this,
118907             view     = me.view,
118908             headerCt = view.headerCt;
118909         headerCt.showMenuBy = me.showMenuBy;
118910         headerCt.getMenuItems = me.getMenuItems();
118911     },
118912     
118913     showMenuBy: function(t, header) {
118914         var menu = this.getMenu(),
118915             groupMenuItem  = menu.down('#groupMenuItem'),
118916             groupableMth = header.groupable === false ?  'disable' : 'enable';
118917             
118918         groupMenuItem[groupableMth]();
118919         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
118920     },
118921     
118922     getMenuItems: function() {
118923         var me                 = this,
118924             groupByText        = me.groupByText,
118925             disabled           = me.disabled,
118926             showGroupsText     = me.showGroupsText,
118927             enableNoGroups     = me.enableNoGroups,
118928             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
118929             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
118930         
118931         // runs in the scope of headerCt
118932         return function() {
118933             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
118934             o.push('-', {
118935                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
118936                 itemId: 'groupMenuItem',
118937                 text: groupByText,
118938                 handler: groupMenuItemClick
118939             });
118940             if (enableNoGroups) {
118941                 o.push({
118942                     itemId: 'groupToggleMenuItem',
118943                     text: showGroupsText,
118944                     checked: !disabled,
118945                     checkHandler: groupToggleMenuItemClick
118946                 });
118947             }
118948             return o;
118949         };
118950     },
118951
118952
118953     /**
118954      * Group by the header the user has clicked on.
118955      * @private
118956      */
118957     onGroupMenuItemClick: function(menuItem, e) {
118958         var me = this,
118959             menu = menuItem.parentMenu,
118960             hdr  = menu.activeHeader,
118961             view = me.view,
118962             store = view.store,
118963             remote = store.remoteGroup;
118964
118965         delete me.lastGroupIndex;
118966         me.block();
118967         me.enable();
118968         store.group(hdr.dataIndex);
118969         me.pruneGroupedHeader();
118970         me.unblock();
118971         if (!remote) {
118972             view.refresh();
118973         }  
118974     },
118975     
118976     block: function(){
118977         this.blockRefresh = this.view.blockRefresh = true;
118978     },
118979     
118980     unblock: function(){
118981         this.blockRefresh = this.view.blockRefresh = false;
118982     },
118983
118984     /**
118985      * Turn on and off grouping via the menu
118986      * @private
118987      */
118988     onGroupToggleMenuItemClick: function(menuItem, checked) {
118989         this[checked ? 'enable' : 'disable']();
118990     },
118991
118992     /**
118993      * Prunes the grouped header from the header container
118994      * @private
118995      */
118996     pruneGroupedHeader: function() {
118997         var me         = this,
118998             view       = me.view,
118999             store      = view.store,
119000             groupField = me.getGroupField(),
119001             headerCt   = view.headerCt,
119002             header     = headerCt.down('header[dataIndex=' + groupField + ']');
119003
119004         if (header) {
119005             if (me.prunedHeader) {
119006                 me.prunedHeader.show();
119007             }
119008             me.prunedHeader = header;
119009             header.hide();
119010         }
119011     },
119012
119013     getGroupField: function(){
119014         var group = this.view.store.groupers.first();
119015         if (group) {
119016             return group.property;    
119017         }
119018         return ''; 
119019     },
119020
119021     /**
119022      * When a row gains focus, expand the groups above it
119023      * @private
119024      */
119025     onRowFocus: function(rowIdx) {
119026         var node    = this.view.getNode(rowIdx),
119027             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
119028
119029         if (groupBd) {
119030             // for multiple level groups, should expand every groupBd
119031             // above
119032             this.expand(groupBd);
119033         }
119034     },
119035
119036     /**
119037      * Expand a group by the groupBody
119038      * @param {Ext.Element} groupBd
119039      * @private
119040      */
119041     expand: function(groupBd) {
119042         var me = this,
119043             view = me.view,
119044             grid = view.up('gridpanel'),
119045             groupBdDom = Ext.getDom(groupBd);
119046             
119047         me.collapsedState[groupBdDom.id] = false;
119048
119049         groupBd.removeCls(me.collapsedCls);
119050         groupBd.prev().removeCls(me.hdCollapsedCls);
119051
119052         grid.determineScrollbars();
119053         grid.invalidateScroller();
119054         view.fireEvent('groupexpand');
119055     },
119056
119057     /**
119058      * Collapse a group by the groupBody
119059      * @param {Ext.Element} groupBd
119060      * @private
119061      */
119062     collapse: function(groupBd) {
119063         var me = this,
119064             view = me.view,
119065             grid = view.up('gridpanel'),
119066             groupBdDom = Ext.getDom(groupBd);
119067             
119068         me.collapsedState[groupBdDom.id] = true;
119069
119070         groupBd.addCls(me.collapsedCls);
119071         groupBd.prev().addCls(me.hdCollapsedCls);
119072
119073         grid.determineScrollbars();
119074         grid.invalidateScroller();
119075         view.fireEvent('groupcollapse');
119076     },
119077     
119078     onGroupChange: function(){
119079         var me = this,
119080             field = me.getGroupField(),
119081             menuItem;
119082             
119083         if (me.hideGroupedHeader) {
119084             if (me.lastGroupField) {
119085                 menuItem = me.getMenuItem(me.lastGroupField);
119086                 if (menuItem) {
119087                     menuItem.setChecked(true);
119088                 }
119089             }
119090             if (field) {
119091                 menuItem = me.getMenuItem(field);
119092                 if (menuItem) {
119093                     menuItem.setChecked(false);
119094                 }
119095             }
119096         }
119097         if (me.blockRefresh !== true) {
119098             me.view.refresh();
119099         }
119100         me.lastGroupField = field;
119101     },
119102     
119103     /**
119104      * Gets the related menu item for a dataIndex
119105      * @private
119106      * @return {Ext.grid.header.Container} The header
119107      */
119108     getMenuItem: function(dataIndex){
119109         var view = this.view,
119110             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
119111             menu = view.headerCt.getMenu();
119112             
119113         return menu.down('menuitem[headerId='+ header.id +']');
119114     },
119115
119116     /**
119117      * Toggle between expanded/collapsed state when clicking on
119118      * the group.
119119      * @private
119120      */
119121     onGroupClick: function(view, group, idx, foo, e) {
119122         var me = this,
119123             toggleCls = me.toggleCls,
119124             groupBd = Ext.fly(group.nextSibling, '_grouping');
119125
119126         if (groupBd.hasCls(me.collapsedCls)) {
119127             me.expand(groupBd);
119128         } else {
119129             me.collapse(groupBd);
119130         }
119131     },
119132
119133     // Injects isRow and closeRow into the metaRowTpl.
119134     getMetaRowTplFragments: function() {
119135         return {
119136             isRow: this.isRow,
119137             closeRow: this.closeRow
119138         };
119139     },
119140
119141     // injected into rowtpl and wrapped around metaRowTpl
119142     // becomes part of the standard tpl
119143     isRow: function() {
119144         return '<tpl if="typeof rows === \'undefined\'">';
119145     },
119146
119147     // injected into rowtpl and wrapped around metaRowTpl
119148     // becomes part of the standard tpl
119149     closeRow: function() {
119150         return '</tpl>';
119151     },
119152
119153     // isRow and closeRow are injected via getMetaRowTplFragments
119154     mutateMetaRowTpl: function(metaRowTpl) {
119155         metaRowTpl.unshift('{[this.isRow()]}');
119156         metaRowTpl.push('{[this.closeRow()]}');
119157     },
119158
119159     // injects an additional style attribute via tdAttrKey with the proper
119160     // amount of padding
119161     getAdditionalData: function(data, idx, record, orig) {
119162         var view = this.view,
119163             hCt  = view.headerCt,
119164             col  = hCt.items.getAt(0),
119165             o = {},
119166             tdAttrKey = col.id + '-tdAttr';
119167
119168         // maintain the current tdAttr that a user may ahve set.
119169         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
119170         o.collapsed = 'true';
119171         return o;
119172     },
119173
119174     // return matching preppedRecords
119175     getGroupRows: function(group, records, preppedRecords, fullWidth) {
119176         var me = this,
119177             children = group.children,
119178             rows = group.rows = [],
119179             view = me.view;
119180         group.viewId = view.id;
119181
119182         Ext.Array.each(records, function(record, idx) {
119183             if (Ext.Array.indexOf(children, record) != -1) {
119184                 rows.push(Ext.apply(preppedRecords[idx], {
119185                     depth: 1
119186                 }));
119187             }
119188         });
119189         delete group.children;
119190         group.fullWidth = fullWidth;
119191         if (me.collapsedState[view.id + '-gp-' + group.name]) {
119192             group.collapsedCls = me.collapsedCls;
119193             group.hdCollapsedCls = me.hdCollapsedCls;
119194         }
119195
119196         return group;
119197     },
119198
119199     // return the data in a grouped format.
119200     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
119201         var me    = this,
119202             store = me.view.store,
119203             groups;
119204             
119205         if (!me.disabled && store.isGrouped()) {
119206             groups = store.getGroups();
119207             Ext.Array.each(groups, function(group, idx){
119208                 me.getGroupRows(group, records, preppedRecords, fullWidth);
119209             }, me);
119210             return {
119211                 rows: groups,
119212                 fullWidth: fullWidth
119213             };
119214         }
119215         return o;
119216     },
119217     
119218     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
119219     // events that are fired on the view. Chose not to return the actual
119220     // group itself because of its expense and because developers can simply
119221     // grab the group via store.getGroups(groupName)
119222     getFireEventArgs: function(type, view, featureTarget, e) {
119223         var returnArray = [type, view, featureTarget],
119224             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
119225             groupBdId   = Ext.getDom(groupBd).id,
119226             prefix      = view.id + '-gp-',
119227             groupName   = groupBdId.substr(prefix.length);
119228         
119229         returnArray.push(groupName, e);
119230         
119231         return returnArray;
119232     }
119233 });
119234
119235 /**
119236  * @class Ext.grid.feature.GroupingSummary
119237  * @extends Ext.grid.feature.Grouping
119238  *
119239  * This feature adds an aggregate summary row at the bottom of each group that is provided
119240  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
119241  *
119242  * ## Calculation
119243  *
119244  * The summary value needs to be calculated for each column in the grid. This is controlled
119245  * by the summaryType option specified on the column. There are several built in summary types,
119246  * which can be specified as a string on the column configuration. These call underlying methods
119247  * on the store:
119248  *
119249  *  - {@link Ext.data.Store#count count}
119250  *  - {@link Ext.data.Store#sum sum}
119251  *  - {@link Ext.data.Store#min min}
119252  *  - {@link Ext.data.Store#max max}
119253  *  - {@link Ext.data.Store#average average}
119254  *
119255  * Alternatively, the summaryType can be a function definition. If this is the case,
119256  * the function is called with an array of records to calculate the summary value.
119257  *
119258  * ## Rendering
119259  *
119260  * Similar to a column, the summary also supports a summaryRenderer function. This
119261  * summaryRenderer is called before displaying a value. The function is optional, if
119262  * not specified the default calculated value is shown. The summaryRenderer is called with:
119263  *
119264  *  - value {Object} - The calculated value.
119265  *  - summaryData {Object} - Contains all raw summary values for the row.
119266  *  - field {String} - The name of the field we are calculating
119267  *
119268  * ## Example Usage
119269  *
119270  *     @example
119271  *     Ext.define('TestResult', {
119272  *         extend: 'Ext.data.Model',
119273  *         fields: ['student', 'subject', {
119274  *             name: 'mark',
119275  *             type: 'int'
119276  *         }]
119277  *     });
119278  *
119279  *     Ext.create('Ext.grid.Panel', {
119280  *         width: 200,
119281  *         height: 240,
119282  *         renderTo: document.body,
119283  *         features: [{
119284  *             groupHeaderTpl: 'Subject: {name}',
119285  *             ftype: 'groupingsummary'
119286  *         }],
119287  *         store: {
119288  *             model: 'TestResult',
119289  *             groupField: 'subject',
119290  *             data: [{
119291  *                 student: 'Student 1',
119292  *                 subject: 'Math',
119293  *                 mark: 84
119294  *             },{
119295  *                 student: 'Student 1',
119296  *                 subject: 'Science',
119297  *                 mark: 72
119298  *             },{
119299  *                 student: 'Student 2',
119300  *                 subject: 'Math',
119301  *                 mark: 96
119302  *             },{
119303  *                 student: 'Student 2',
119304  *                 subject: 'Science',
119305  *                 mark: 68
119306  *             }]
119307  *         },
119308  *         columns: [{
119309  *             dataIndex: 'student',
119310  *             text: 'Name',
119311  *             summaryType: 'count',
119312  *             summaryRenderer: function(value){
119313  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
119314  *             }
119315  *         }, {
119316  *             dataIndex: 'mark',
119317  *             text: 'Mark',
119318  *             summaryType: 'average'
119319  *         }]
119320  *     });
119321  */
119322 Ext.define('Ext.grid.feature.GroupingSummary', {
119323
119324     /* Begin Definitions */
119325
119326     extend: 'Ext.grid.feature.Grouping',
119327
119328     alias: 'feature.groupingsummary',
119329
119330     mixins: {
119331         summary: 'Ext.grid.feature.AbstractSummary'
119332     },
119333
119334     /* End Definitions */
119335
119336
119337    /**
119338     * Modifies the row template to include the summary row.
119339     * @private
119340     * @return {String} The modified template
119341     */
119342    getFeatureTpl: function() {
119343         var tpl = this.callParent(arguments);
119344
119345         if (this.showSummaryRow) {
119346             // lop off the end </tpl> so we can attach it
119347             tpl = tpl.replace('</tpl>', '');
119348             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
119349         }
119350         return tpl;
119351     },
119352
119353     /**
119354      * Gets any fragments needed for the template.
119355      * @private
119356      * @return {Object} The fragments
119357      */
119358     getFragmentTpl: function() {
119359         var me = this,
119360             fragments = me.callParent();
119361
119362         Ext.apply(fragments, me.getSummaryFragments());
119363         if (me.showSummaryRow) {
119364             // this gets called before render, so we'll setup the data here.
119365             me.summaryGroups = me.view.store.getGroups();
119366             me.summaryData = me.generateSummaryData();
119367         }
119368         return fragments;
119369     },
119370
119371     /**
119372      * Gets the data for printing a template row
119373      * @private
119374      * @param {Number} index The index in the template
119375      * @return {Array} The template values
119376      */
119377     getPrintData: function(index){
119378         var me = this,
119379             columns = me.view.headerCt.getColumnsForTpl(),
119380             i = 0,
119381             length = columns.length,
119382             data = [],
119383             name = me.summaryGroups[index - 1].name,
119384             active = me.summaryData[name],
119385             column;
119386
119387         for (; i < length; ++i) {
119388             column = columns[i];
119389             column.gridSummaryValue = this.getColumnValue(column, active);
119390             data.push(column);
119391         }
119392         return data;
119393     },
119394
119395     /**
119396      * Generates all of the summary data to be used when processing the template
119397      * @private
119398      * @return {Object} The summary data
119399      */
119400     generateSummaryData: function(){
119401         var me = this,
119402             data = {},
119403             remoteData = {},
119404             store = me.view.store,
119405             groupField = this.getGroupField(),
119406             reader = store.proxy.reader,
119407             groups = me.summaryGroups,
119408             columns = me.view.headerCt.getColumnsForTpl(),
119409             remote,
119410             i,
119411             length,
119412             fieldData,
119413             root,
119414             key,
119415             comp;
119416
119417         for (i = 0, length = groups.length; i < length; ++i) {
119418             data[groups[i].name] = {};
119419         }
119420
119421         /**
119422          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
119423          * summary objects. It allows to use server-side calculated summaries.
119424          */
119425         if (me.remoteRoot && reader.rawData) {
119426             // reset reader root and rebuild extractors to extract summaries data
119427             root = reader.root;
119428             reader.root = me.remoteRoot;
119429             reader.buildExtractors(true);
119430             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
119431                  remoteData[value[groupField]] = value;
119432             });
119433             // restore initial reader configuration
119434             reader.root = root;
119435             reader.buildExtractors(true);
119436         }
119437
119438         for (i = 0, length = columns.length; i < length; ++i) {
119439             comp = Ext.getCmp(columns[i].id);
119440             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
119441
119442             for (key in fieldData) {
119443                 if (fieldData.hasOwnProperty(key)) {
119444                     data[key][comp.id] = fieldData[key];
119445                 }
119446             }
119447
119448             for (key in remoteData) {
119449                 if (remoteData.hasOwnProperty(key)) {
119450                     remote = remoteData[key][comp.dataIndex];
119451                     if (remote !== undefined && data[key] !== undefined) {
119452                         data[key][comp.id] = remote;
119453                     }
119454                 }
119455             }
119456         }
119457         return data;
119458     }
119459 });
119460
119461 /**
119462  * @class Ext.grid.feature.RowBody
119463  * @extends Ext.grid.feature.Feature
119464  *
119465  * The rowbody feature enhances the grid's markup to have an additional
119466  * tr -> td -> div which spans the entire width of the original row.
119467  *
119468  * This is useful to to associate additional information with a particular
119469  * record in a grid.
119470  *
119471  * Rowbodies are initially hidden unless you override getAdditionalData.
119472  *
119473  * Will expose additional events on the gridview with the prefix of 'rowbody'.
119474  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
119475  *
119476  * @ftype rowbody
119477  */
119478 Ext.define('Ext.grid.feature.RowBody', {
119479     extend: 'Ext.grid.feature.Feature',
119480     alias: 'feature.rowbody',
119481     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
119482     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
119483     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
119484     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
119485
119486     eventPrefix: 'rowbody',
119487     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
119488     
119489     getRowBody: function(values) {
119490         return [
119491             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
119492                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
119493                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
119494                 '</td>',
119495             '</tr>'
119496         ].join('');
119497     },
119498     
119499     // injects getRowBody into the metaRowTpl.
119500     getMetaRowTplFragments: function() {
119501         return {
119502             getRowBody: this.getRowBody,
119503             rowBodyTrCls: this.rowBodyTrCls,
119504             rowBodyTdCls: this.rowBodyTdCls,
119505             rowBodyDivCls: this.rowBodyDivCls
119506         };
119507     },
119508
119509     mutateMetaRowTpl: function(metaRowTpl) {
119510         metaRowTpl.push('{[this.getRowBody(values)]}');
119511     },
119512
119513     /**
119514      * Provide additional data to the prepareData call within the grid view.
119515      * The rowbody feature adds 3 additional variables into the grid view's template.
119516      * These are rowBodyCls, rowBodyColspan, and rowBody.
119517      * @param {Object} data The data for this particular record.
119518      * @param {Number} idx The row index for this record.
119519      * @param {Ext.data.Model} record The record instance
119520      * @param {Object} orig The original result from the prepareData call to massage.
119521      */
119522     getAdditionalData: function(data, idx, record, orig) {
119523         var headerCt = this.view.headerCt,
119524             colspan  = headerCt.getColumnCount();
119525
119526         return {
119527             rowBody: "",
119528             rowBodyCls: this.rowBodyCls,
119529             rowBodyColspan: colspan
119530         };
119531     }
119532 });
119533 /**
119534  * @class Ext.grid.feature.RowWrap
119535  * @extends Ext.grid.feature.Feature
119536  * @private
119537  */
119538 Ext.define('Ext.grid.feature.RowWrap', {
119539     extend: 'Ext.grid.feature.Feature',
119540     alias: 'feature.rowwrap',
119541
119542     // turn off feature events.
119543     hasFeatureEvent: false,
119544     
119545     mutateMetaRowTpl: function(metaRowTpl) {        
119546         // Remove "x-grid-row" from the first row, note this could be wrong
119547         // if some other feature unshifted things in front.
119548         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
119549         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
119550         // 2
119551         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
119552         // 1
119553         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
119554         
119555         // 3
119556         metaRowTpl.push('</table>');
119557         // 4
119558         metaRowTpl.push('</div></td></tr>');
119559     },
119560     
119561     embedColSpan: function() {
119562         return '{colspan}';
119563     },
119564     
119565     embedFullWidth: function() {
119566         return '{fullWidth}';
119567     },
119568     
119569     getAdditionalData: function(data, idx, record, orig) {
119570         var headerCt = this.view.headerCt,
119571             colspan  = headerCt.getColumnCount(),
119572             fullWidth = headerCt.getFullWidth(),
119573             items    = headerCt.query('gridcolumn'),
119574             itemsLn  = items.length,
119575             i = 0,
119576             o = {
119577                 colspan: colspan,
119578                 fullWidth: fullWidth
119579             },
119580             id,
119581             tdClsKey,
119582             colResizerCls;
119583
119584         for (; i < itemsLn; i++) {
119585             id = items[i].id;
119586             tdClsKey = id + '-tdCls';
119587             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
119588             // give the inner td's the resizer class
119589             // while maintaining anything a user may have injected via a custom
119590             // renderer
119591             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
119592             // TODO: Unhackify the initial rendering width's
119593             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
119594             if (orig[id+'-tdAttr']) {
119595                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
119596             }
119597             
119598         }
119599
119600         return o;
119601     },
119602     
119603     getMetaRowTplFragments: function() {
119604         return {
119605             embedFullWidth: this.embedFullWidth,
119606             embedColSpan: this.embedColSpan
119607         };
119608     }
119609     
119610 });
119611 /**
119612  * @class Ext.grid.feature.Summary
119613  * @extends Ext.grid.feature.AbstractSummary
119614  * 
119615  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
119616  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
119617  * calculation and rendering.
119618  * 
119619  * ## Calculation
119620  * The summary value needs to be calculated for each column in the grid. This is controlled
119621  * by the summaryType option specified on the column. There are several built in summary types,
119622  * which can be specified as a string on the column configuration. These call underlying methods
119623  * on the store:
119624  *
119625  *  - {@link Ext.data.Store#count count}
119626  *  - {@link Ext.data.Store#sum sum}
119627  *  - {@link Ext.data.Store#min min}
119628  *  - {@link Ext.data.Store#max max}
119629  *  - {@link Ext.data.Store#average average}
119630  *
119631  * Alternatively, the summaryType can be a function definition. If this is the case,
119632  * the function is called with an array of records to calculate the summary value.
119633  * 
119634  * ## Rendering
119635  * Similar to a column, the summary also supports a summaryRenderer function. This
119636  * summaryRenderer is called before displaying a value. The function is optional, if
119637  * not specified the default calculated value is shown. The summaryRenderer is called with:
119638  *
119639  *  - value {Object} - The calculated value.
119640  *  - summaryData {Object} - Contains all raw summary values for the row.
119641  *  - field {String} - The name of the field we are calculating
119642  * 
119643  * ## Example Usage
119644  *
119645  *     @example
119646  *     Ext.define('TestResult', {
119647  *         extend: 'Ext.data.Model',
119648  *         fields: ['student', {
119649  *             name: 'mark',
119650  *             type: 'int'
119651  *         }]
119652  *     });
119653  *     
119654  *     Ext.create('Ext.grid.Panel', {
119655  *         width: 200,
119656  *         height: 140,
119657  *         renderTo: document.body,
119658  *         features: [{
119659  *             ftype: 'summary'
119660  *         }],
119661  *         store: {
119662  *             model: 'TestResult',
119663  *             data: [{
119664  *                 student: 'Student 1',
119665  *                 mark: 84
119666  *             },{
119667  *                 student: 'Student 2',
119668  *                 mark: 72
119669  *             },{
119670  *                 student: 'Student 3',
119671  *                 mark: 96
119672  *             },{
119673  *                 student: 'Student 4',
119674  *                 mark: 68
119675  *             }]
119676  *         },
119677  *         columns: [{
119678  *             dataIndex: 'student',
119679  *             text: 'Name',
119680  *             summaryType: 'count',
119681  *             summaryRenderer: function(value, summaryData, dataIndex) {
119682  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
119683  *             }
119684  *         }, {
119685  *             dataIndex: 'mark',
119686  *             text: 'Mark',
119687  *             summaryType: 'average'
119688  *         }]
119689  *     });
119690  */
119691 Ext.define('Ext.grid.feature.Summary', {
119692     
119693     /* Begin Definitions */
119694     
119695     extend: 'Ext.grid.feature.AbstractSummary',
119696     
119697     alias: 'feature.summary',
119698     
119699     /* End Definitions */
119700     
119701     /**
119702      * Gets any fragments needed for the template.
119703      * @private
119704      * @return {Object} The fragments
119705      */
119706     getFragmentTpl: function() {
119707         // this gets called before render, so we'll setup the data here.
119708         this.summaryData = this.generateSummaryData(); 
119709         return this.getSummaryFragments();
119710     },
119711     
119712     /**
119713      * Overrides the closeRows method on the template so we can include our own custom
119714      * footer.
119715      * @private
119716      * @return {Object} The custom fragments
119717      */
119718     getTableFragments: function(){
119719         if (this.showSummaryRow) {
119720             return {
119721                 closeRows: this.closeRows
119722             };
119723         }
119724     },
119725     
119726     /**
119727      * Provide our own custom footer for the grid.
119728      * @private
119729      * @return {String} The custom footer
119730      */
119731     closeRows: function() {
119732         return '</tpl>{[this.printSummaryRow()]}';
119733     },
119734     
119735     /**
119736      * Gets the data for printing a template row
119737      * @private
119738      * @param {Number} index The index in the template
119739      * @return {Array} The template values
119740      */
119741     getPrintData: function(index){
119742         var me = this,
119743             columns = me.view.headerCt.getColumnsForTpl(),
119744             i = 0,
119745             length = columns.length,
119746             data = [],
119747             active = me.summaryData,
119748             column;
119749             
119750         for (; i < length; ++i) {
119751             column = columns[i];
119752             column.gridSummaryValue = this.getColumnValue(column, active);
119753             data.push(column);
119754         }
119755         return data;
119756     },
119757     
119758     /**
119759      * Generates all of the summary data to be used when processing the template
119760      * @private
119761      * @return {Object} The summary data
119762      */
119763     generateSummaryData: function(){
119764         var me = this,
119765             data = {},
119766             store = me.view.store,
119767             columns = me.view.headerCt.getColumnsForTpl(),
119768             i = 0,
119769             length = columns.length,
119770             fieldData,
119771             key,
119772             comp;
119773             
119774         for (i = 0, length = columns.length; i < length; ++i) {
119775             comp = Ext.getCmp(columns[i].id);
119776             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
119777         }
119778         return data;
119779     }
119780 });
119781 /**
119782  * @class Ext.grid.header.DragZone
119783  * @extends Ext.dd.DragZone
119784  * @private
119785  */
119786 Ext.define('Ext.grid.header.DragZone', {
119787     extend: 'Ext.dd.DragZone',
119788     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119789     maxProxyWidth: 120,
119790
119791     constructor: function(headerCt) {
119792         this.headerCt = headerCt;
119793         this.ddGroup =  this.getDDGroup();
119794         this.callParent([headerCt.el]);
119795         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
119796     },
119797
119798     getDDGroup: function() {
119799         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119800     },
119801
119802     getDragData: function(e) {
119803         var header = e.getTarget('.'+this.colHeaderCls),
119804             headerCmp;
119805
119806         if (header) {
119807             headerCmp = Ext.getCmp(header.id);
119808             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
119809                 var ddel = document.createElement('div');
119810                 ddel.innerHTML = Ext.getCmp(header.id).text;
119811                 return {
119812                     ddel: ddel,
119813                     header: headerCmp
119814                 };
119815             }
119816         }
119817         return false;
119818     },
119819
119820     onBeforeDrag: function() {
119821         return !(this.headerCt.dragging || this.disabled);
119822     },
119823
119824     onInitDrag: function() {
119825         this.headerCt.dragging = true;
119826         this.callParent(arguments);
119827     },
119828
119829     onDragDrop: function() {
119830         this.headerCt.dragging = false;
119831         this.callParent(arguments);
119832     },
119833
119834     afterRepair: function() {
119835         this.callParent();
119836         this.headerCt.dragging = false;
119837     },
119838
119839     getRepairXY: function() {
119840         return this.dragData.header.el.getXY();
119841     },
119842     
119843     disable: function() {
119844         this.disabled = true;
119845     },
119846     
119847     enable: function() {
119848         this.disabled = false;
119849     }
119850 });
119851
119852 /**
119853  * @class Ext.grid.header.DropZone
119854  * @extends Ext.dd.DropZone
119855  * @private
119856  */
119857 Ext.define('Ext.grid.header.DropZone', {
119858     extend: 'Ext.dd.DropZone',
119859     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119860     proxyOffsets: [-4, -9],
119861
119862     constructor: function(headerCt){
119863         this.headerCt = headerCt;
119864         this.ddGroup = this.getDDGroup();
119865         this.callParent([headerCt.el]);
119866     },
119867
119868     getDDGroup: function() {
119869         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119870     },
119871
119872     getTargetFromEvent : function(e){
119873         return e.getTarget('.' + this.colHeaderCls);
119874     },
119875
119876     getTopIndicator: function() {
119877         if (!this.topIndicator) {
119878             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
119879                 cls: "col-move-top",
119880                 html: "&#160;"
119881             }, true);
119882         }
119883         return this.topIndicator;
119884     },
119885
119886     getBottomIndicator: function() {
119887         if (!this.bottomIndicator) {
119888             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
119889                 cls: "col-move-bottom",
119890                 html: "&#160;"
119891             }, true);
119892         }
119893         return this.bottomIndicator;
119894     },
119895
119896     getLocation: function(e, t) {
119897         var x      = e.getXY()[0],
119898             region = Ext.fly(t).getRegion(),
119899             pos, header;
119900
119901         if ((region.right - x) <= (region.right - region.left) / 2) {
119902             pos = "after";
119903         } else {
119904             pos = "before";
119905         }
119906         return {
119907             pos: pos,
119908             header: Ext.getCmp(t.id),
119909             node: t
119910         };
119911     },
119912
119913     positionIndicator: function(draggedHeader, node, e){
119914         var location = this.getLocation(e, node),
119915             header = location.header,
119916             pos    = location.pos,
119917             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
119918             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
119919             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
119920             topXY, bottomXY, headerCtEl, minX, maxX;
119921
119922         // Cannot drag beyond non-draggable start column
119923         if (!header.draggable && header.getIndex() == 0) {
119924             return false;
119925         }
119926
119927         this.lastLocation = location;
119928
119929         if ((draggedHeader !== header) &&
119930             ((pos === "before" && nextHd !== header) ||
119931             (pos === "after" && prevHd !== header)) &&
119932             !header.isDescendantOf(draggedHeader)) {
119933
119934             // As we move in between different DropZones that are in the same
119935             // group (such as the case when in a locked grid), invalidateDrop
119936             // on the other dropZones.
119937             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
119938                 ln = allDropZones.length,
119939                 i  = 0,
119940                 dropZone;
119941
119942             for (; i < ln; i++) {
119943                 dropZone = allDropZones[i];
119944                 if (dropZone !== this && dropZone.invalidateDrop) {
119945                     dropZone.invalidateDrop();
119946                 }
119947             }
119948
119949
119950             this.valid = true;
119951             topIndicator = this.getTopIndicator();
119952             bottomIndicator = this.getBottomIndicator();
119953             if (pos === 'before') {
119954                 topAnchor = 'tl';
119955                 bottomAnchor = 'bl';
119956             } else {
119957                 topAnchor = 'tr';
119958                 bottomAnchor = 'br';
119959             }
119960             topXY = header.el.getAnchorXY(topAnchor);
119961             bottomXY = header.el.getAnchorXY(bottomAnchor);
119962
119963             // constrain the indicators to the viewable section
119964             headerCtEl = this.headerCt.el;
119965             minX = headerCtEl.getLeft();
119966             maxX = headerCtEl.getRight();
119967
119968             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
119969             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
119970
119971             // adjust by offsets, this is to center the arrows so that they point
119972             // at the split point
119973             topXY[0] -= 4;
119974             topXY[1] -= 9;
119975             bottomXY[0] -= 4;
119976
119977             // position and show indicators
119978             topIndicator.setXY(topXY);
119979             bottomIndicator.setXY(bottomXY);
119980             topIndicator.show();
119981             bottomIndicator.show();
119982         // invalidate drop operation and hide indicators
119983         } else {
119984             this.invalidateDrop();
119985         }
119986     },
119987
119988     invalidateDrop: function() {
119989         this.valid = false;
119990         this.hideIndicators();
119991     },
119992
119993     onNodeOver: function(node, dragZone, e, data) {
119994         if (data.header.el.dom !== node) {
119995             this.positionIndicator(data.header, node, e);
119996         }
119997         return this.valid ? this.dropAllowed : this.dropNotAllowed;
119998     },
119999
120000     hideIndicators: function() {
120001         this.getTopIndicator().hide();
120002         this.getBottomIndicator().hide();
120003     },
120004
120005     onNodeOut: function() {
120006         this.hideIndicators();
120007     },
120008
120009     onNodeDrop: function(node, dragZone, e, data) {
120010         if (this.valid) {
120011             this.invalidateDrop();
120012             var hd = data.header,
120013                 lastLocation = this.lastLocation,
120014                 fromCt  = hd.ownerCt,
120015                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
120016                 toCt    = lastLocation.header.ownerCt,
120017                 toIdx   = toCt.items.indexOf(lastLocation.header),
120018                 headerCt = this.headerCt,
120019                 groupCt,
120020                 scrollerOwner;
120021
120022             if (lastLocation.pos === 'after') {
120023                 toIdx++;
120024             }
120025
120026             // If we are dragging in between two HeaderContainers that have had the lockable
120027             // mixin injected we will lock/unlock headers in between sections. Note that lockable
120028             // does NOT currently support grouped headers.
120029             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
120030                 scrollerOwner = fromCt.up('[scrollerOwner]');
120031                 scrollerOwner.lock(hd, toIdx);
120032             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
120033                 scrollerOwner = fromCt.up('[scrollerOwner]');
120034                 scrollerOwner.unlock(hd, toIdx);
120035             } else {
120036                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
120037                 // in between the same container.
120038                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
120039                     toIdx--;
120040                 }
120041
120042                 // Remove dragged header from where it was without destroying it or relaying its Container
120043                 if (fromCt !== toCt) {
120044                     fromCt.suspendLayout = true;
120045                     fromCt.remove(hd, false);
120046                     fromCt.suspendLayout = false;
120047                 }
120048
120049                 // Dragged the last header out of the fromCt group... The fromCt group must die
120050                 if (fromCt.isGroupHeader) {
120051                     if (!fromCt.items.getCount()) {
120052                         groupCt = fromCt.ownerCt;
120053                         groupCt.suspendLayout = true;
120054                         groupCt.remove(fromCt, false);
120055                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
120056                         groupCt.suspendLayout = false;
120057                     } else {
120058                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
120059                         fromCt.setWidth(fromCt.minWidth);
120060                     }
120061                 }
120062
120063                 // Move dragged header into its drop position
120064                 toCt.suspendLayout = true;
120065                 if (fromCt === toCt) {
120066                     toCt.move(fromIdx, toIdx);
120067                 } else {
120068                     toCt.insert(toIdx, hd);
120069                 }
120070                 toCt.suspendLayout = false;
120071
120072                 // Group headers acquire the aggregate width of their child headers
120073                 // Therefore a child header may not flex; it must contribute a fixed width.
120074                 // But we restore the flex value when moving back into the main header container
120075                 if (toCt.isGroupHeader) {
120076                     hd.savedFlex = hd.flex;
120077                     delete hd.flex;
120078                     hd.width = hd.getWidth();
120079                     // When there was previously a flex, we need to ensure we don't count for the
120080                     // border twice.
120081                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
120082                     toCt.setWidth(toCt.minWidth);
120083                 } else {
120084                     if (hd.savedFlex) {
120085                         hd.flex = hd.savedFlex;
120086                         delete hd.width;
120087                     }
120088                 }
120089
120090
120091                 // Refresh columns cache in case we remove an emptied group column
120092                 headerCt.purgeCache();
120093                 headerCt.doLayout();
120094                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
120095                 // Emptied group header can only be destroyed after the header and grid have been refreshed
120096                 if (!fromCt.items.getCount()) {
120097                     fromCt.destroy();
120098                 }
120099             }
120100         }
120101     }
120102 });
120103
120104 /**
120105  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
120106  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
120107  * in the {@link Ext.grid.column.Column column configuration}.
120108  *
120109  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
120110  * {@link Ext.grid.plugin.RowEditing}.
120111  */
120112 Ext.define('Ext.grid.plugin.Editing', {
120113     alias: 'editing.editing',
120114
120115     requires: [
120116         'Ext.grid.column.Column',
120117         'Ext.util.KeyNav'
120118     ],
120119
120120     mixins: {
120121         observable: 'Ext.util.Observable'
120122     },
120123
120124     /**
120125      * @cfg {Number} clicksToEdit
120126      * The number of clicks on a grid required to display the editor.
120127      */
120128     clicksToEdit: 2,
120129
120130     // private
120131     defaultFieldXType: 'textfield',
120132
120133     // cell, row, form
120134     editStyle: '',
120135
120136     constructor: function(config) {
120137         var me = this;
120138         Ext.apply(me, config);
120139
120140         me.addEvents(
120141             // Doc'ed in separate editing plugins
120142             'beforeedit',
120143
120144             // Doc'ed in separate editing plugins
120145             'edit',
120146
120147             // Doc'ed in separate editing plugins
120148             'validateedit'
120149         );
120150         me.mixins.observable.constructor.call(me);
120151         // TODO: Deprecated, remove in 5.0
120152         me.relayEvents(me, ['afteredit'], 'after');
120153     },
120154
120155     // private
120156     init: function(grid) {
120157         var me = this;
120158
120159         me.grid = grid;
120160         me.view = grid.view;
120161         me.initEvents();
120162         me.mon(grid, 'reconfigure', me.onReconfigure, me);
120163         me.onReconfigure();
120164
120165         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
120166         // Marks the grid as editable, so that the SelectionModel
120167         // can make appropriate decisions during navigation
120168         grid.isEditable = true;
120169         grid.editingPlugin = grid.view.editingPlugin = me;
120170     },
120171
120172     /**
120173      * Fires after the grid is reconfigured
120174      * @private
120175      */
120176     onReconfigure: function(){
120177         this.initFieldAccessors(this.view.getGridColumns());
120178     },
120179
120180     /**
120181      * @private
120182      * AbstractComponent calls destroy on all its plugins at destroy time.
120183      */
120184     destroy: function() {
120185         var me = this,
120186             grid = me.grid,
120187             headerCt = grid.headerCt,
120188             events = grid.events;
120189
120190         Ext.destroy(me.keyNav);
120191         me.removeFieldAccessors(grid.getView().getGridColumns());
120192
120193         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
120194         me.clearListeners();
120195
120196         delete me.grid.editingPlugin;
120197         delete me.grid.view.editingPlugin;
120198         delete me.grid;
120199         delete me.view;
120200         delete me.editor;
120201         delete me.keyNav;
120202     },
120203
120204     // private
120205     getEditStyle: function() {
120206         return this.editStyle;
120207     },
120208
120209     // private
120210     initFieldAccessors: function(column) {
120211         var me = this;
120212
120213         if (Ext.isArray(column)) {
120214             Ext.Array.forEach(column, me.initFieldAccessors, me);
120215             return;
120216         }
120217
120218         // Augment the Header class to have a getEditor and setEditor method
120219         // Important: Only if the header does not have its own implementation.
120220         Ext.applyIf(column, {
120221             getEditor: function(record, defaultField) {
120222                 return me.getColumnField(this, defaultField);
120223             },
120224
120225             setEditor: function(field) {
120226                 me.setColumnField(this, field);
120227             }
120228         });
120229     },
120230
120231     // private
120232     removeFieldAccessors: function(column) {
120233         var me = this;
120234
120235         if (Ext.isArray(column)) {
120236             Ext.Array.forEach(column, me.removeFieldAccessors, me);
120237             return;
120238         }
120239
120240         delete column.getEditor;
120241         delete column.setEditor;
120242     },
120243
120244     // private
120245     // remaps to the public API of Ext.grid.column.Column.getEditor
120246     getColumnField: function(columnHeader, defaultField) {
120247         var field = columnHeader.field;
120248
120249         if (!field && columnHeader.editor) {
120250             field = columnHeader.editor;
120251             delete columnHeader.editor;
120252         }
120253
120254         if (!field && defaultField) {
120255             field = defaultField;
120256         }
120257
120258         if (field) {
120259             if (Ext.isString(field)) {
120260                 field = { xtype: field };
120261             }
120262             if (Ext.isObject(field) && !field.isFormField) {
120263                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120264                 columnHeader.field = field;
120265             }
120266
120267             Ext.apply(field, {
120268                 name: columnHeader.dataIndex
120269             });
120270
120271             return field;
120272         }
120273     },
120274
120275     // private
120276     // remaps to the public API of Ext.grid.column.Column.setEditor
120277     setColumnField: function(column, field) {
120278         if (Ext.isObject(field) && !field.isFormField) {
120279             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120280         }
120281         column.field = field;
120282     },
120283
120284     // private
120285     initEvents: function() {
120286         var me = this;
120287         me.initEditTriggers();
120288         me.initCancelTriggers();
120289     },
120290
120291     // @abstract
120292     initCancelTriggers: Ext.emptyFn,
120293
120294     // private
120295     initEditTriggers: function() {
120296         var me = this,
120297             view = me.view,
120298             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
120299
120300         // Start editing
120301         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
120302         view.on('render', function() {
120303             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
120304                 enter: me.onEnterKey,
120305                 esc: me.onEscKey,
120306                 scope: me
120307             });
120308         }, me, { single: true });
120309     },
120310
120311     // private
120312     onEnterKey: function(e) {
120313         var me = this,
120314             grid = me.grid,
120315             selModel = grid.getSelectionModel(),
120316             record,
120317             columnHeader = grid.headerCt.getHeaderAtIndex(0);
120318
120319         // Calculate editing start position from SelectionModel
120320         // CellSelectionModel
120321         if (selModel.getCurrentPosition) {
120322             pos = selModel.getCurrentPosition();
120323             record = grid.store.getAt(pos.row);
120324             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
120325         }
120326         // RowSelectionModel
120327         else {
120328             record = selModel.getLastSelected();
120329         }
120330         me.startEdit(record, columnHeader);
120331     },
120332
120333     // private
120334     onEscKey: function(e) {
120335         this.cancelEdit();
120336     },
120337
120338     // private
120339     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
120340         this.startEdit(record, view.getHeaderAtIndex(colIdx));
120341     },
120342
120343     /**
120344      * @private
120345      * @template
120346      * Template method called before editing begins.
120347      * @param {Object} context The current editing context
120348      * @return {Boolean} Return false to cancel the editing process
120349      */
120350     beforeEdit: Ext.emptyFn,
120351
120352     /**
120353      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120354      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
120355      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
120356      */
120357     startEdit: function(record, columnHeader) {
120358         var me = this,
120359             context = me.getEditingContext(record, columnHeader);
120360
120361         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120362             return false;
120363         }
120364
120365         me.context = context;
120366         me.editing = true;
120367     },
120368
120369     /**
120370      * @private
120371      * Collects all information necessary for any subclasses to perform their editing functions.
120372      * @param record
120373      * @param columnHeader
120374      * @returns {Object} The editing context based upon the passed record and column
120375      */
120376     getEditingContext: function(record, columnHeader) {
120377         var me = this,
120378             grid = me.grid,
120379             store = grid.store,
120380             rowIdx,
120381             colIdx,
120382             view = grid.getView(),
120383             value;
120384
120385         // If they'd passed numeric row, column indices, look them up.
120386         if (Ext.isNumber(record)) {
120387             rowIdx = record;
120388             record = store.getAt(rowIdx);
120389         } else {
120390             rowIdx = store.indexOf(record);
120391         }
120392         if (Ext.isNumber(columnHeader)) {
120393             colIdx = columnHeader;
120394             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
120395         } else {
120396             colIdx = columnHeader.getIndex();
120397         }
120398
120399         value = record.get(columnHeader.dataIndex);
120400         return {
120401             grid: grid,
120402             record: record,
120403             field: columnHeader.dataIndex,
120404             value: value,
120405             row: view.getNode(rowIdx),
120406             column: columnHeader,
120407             rowIdx: rowIdx,
120408             colIdx: colIdx
120409         };
120410     },
120411
120412     /**
120413      * Cancels any active edit that is in progress.
120414      */
120415     cancelEdit: function() {
120416         this.editing = false;
120417     },
120418
120419     /**
120420      * Completes the edit if there is an active edit in progress.
120421      */
120422     completeEdit: function() {
120423         var me = this;
120424
120425         if (me.editing && me.validateEdit()) {
120426             me.fireEvent('edit', me.context);
120427         }
120428
120429         delete me.context;
120430         me.editing = false;
120431     },
120432
120433     // @abstract
120434     validateEdit: function() {
120435         var me = this,
120436             context = me.context;
120437
120438         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
120439     }
120440 });
120441
120442 /**
120443  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
120444  * cell will be editable at a time. The field that will be used for the editor is defined at the
120445  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
120446  *
120447  * If an editor is not specified for a particular column then that cell will not be editable and it will
120448  * be skipped when activated via the mouse or the keyboard.
120449  *
120450  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
120451  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
120452  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
120453  *
120454  *     @example
120455  *     Ext.create('Ext.data.Store', {
120456  *         storeId:'simpsonsStore',
120457  *         fields:['name', 'email', 'phone'],
120458  *         data:{'items':[
120459  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
120460  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
120461  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
120462  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
120463  *         ]},
120464  *         proxy: {
120465  *             type: 'memory',
120466  *             reader: {
120467  *                 type: 'json',
120468  *                 root: 'items'
120469  *             }
120470  *         }
120471  *     });
120472  *
120473  *     Ext.create('Ext.grid.Panel', {
120474  *         title: 'Simpsons',
120475  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
120476  *         columns: [
120477  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
120478  *             {header: 'Email', dataIndex: 'email', flex:1,
120479  *                 editor: {
120480  *                     xtype: 'textfield',
120481  *                     allowBlank: false
120482  *                 }
120483  *             },
120484  *             {header: 'Phone', dataIndex: 'phone'}
120485  *         ],
120486  *         selType: 'cellmodel',
120487  *         plugins: [
120488  *             Ext.create('Ext.grid.plugin.CellEditing', {
120489  *                 clicksToEdit: 1
120490  *             })
120491  *         ],
120492  *         height: 200,
120493  *         width: 400,
120494  *         renderTo: Ext.getBody()
120495  *     });
120496  */
120497 Ext.define('Ext.grid.plugin.CellEditing', {
120498     alias: 'plugin.cellediting',
120499     extend: 'Ext.grid.plugin.Editing',
120500     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
120501
120502     constructor: function() {
120503         /**
120504          * @event beforeedit
120505          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
120506          *
120507          * @param {Object} e An edit event with the following properties:
120508          *
120509          * - grid - The grid
120510          * - record - The record being edited
120511          * - field - The field name being edited
120512          * - value - The value for the field being edited.
120513          * - row - The grid table row
120514          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120515          * - rowIdx - The row index that is being edited
120516          * - colIdx - The column index that is being edited
120517          * - cancel - Set this to true to cancel the edit or return false from your handler.
120518          */
120519         /**
120520          * @event edit
120521          * Fires after a cell is edited. Usage example:
120522          *
120523          *     grid.on('edit', function(editor, e) {
120524          *         // commit the changes right after editing finished
120525          *         e.record.commit();
120526          *     };
120527          *
120528          * @param {Ext.grid.plugin.Editing} editor
120529          * @param {Object} e An edit event with the following properties:
120530          *
120531          * - grid - The grid
120532          * - record - The record that was edited
120533          * - field - The field name that was edited
120534          * - value - The value being set
120535          * - originalValue - The original value for the field, before the edit.
120536          * - row - The grid table row
120537          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
120538          * - rowIdx - The row index that was edited
120539          * - colIdx - The column index that was edited
120540          */
120541         /**
120542          * @event validateedit
120543          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
120544          * cancel the change.
120545          *
120546          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
120547          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
120548          * example) and then setting the field's new value in the Record directly:
120549          *
120550          *     grid.on('validateedit', function(editor, e) {
120551          *       var myTargetRow = 6;
120552          *
120553          *       if (e.row == myTargetRow) {
120554          *         e.cancel = true;
120555          *         e.record.data[e.field] = e.value;
120556          *       }
120557          *     });
120558          *
120559          * @param {Ext.grid.plugin.Editing} editor
120560          * @param {Object} e An edit event with the following properties:
120561          *
120562          * - grid - The grid
120563          * - record - The record being edited
120564          * - field - The field name being edited
120565          * - value - The value being set
120566          * - originalValue - The original value for the field, before the edit.
120567          * - row - The grid table row
120568          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120569          * - rowIdx - The row index that is being edited
120570          * - colIdx - The column index that is being edited
120571          * - cancel - Set this to true to cancel the edit or return false from your handler.
120572          */
120573         this.callParent(arguments);
120574         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
120575             return editor.editorId;
120576         });
120577         this.editTask = Ext.create('Ext.util.DelayedTask');
120578     },
120579     
120580     onReconfigure: function(){
120581         this.editors.clear();
120582         this.callParent();    
120583     },
120584
120585     /**
120586      * @private
120587      * AbstractComponent calls destroy on all its plugins at destroy time.
120588      */
120589     destroy: function() {
120590         var me = this;
120591         me.editTask.cancel();
120592         me.editors.each(Ext.destroy, Ext);
120593         me.editors.clear();
120594         me.callParent(arguments);
120595     },
120596     
120597     onBodyScroll: function() {
120598         var ed = this.getActiveEditor();
120599         if (ed && ed.field) {
120600             if (ed.field.triggerBlur) {
120601                 ed.field.triggerBlur();
120602             } else {
120603                 ed.field.blur();
120604             }
120605         }
120606     },
120607
120608     // private
120609     // Template method called from base class's initEvents
120610     initCancelTriggers: function() {
120611         var me   = this,
120612             grid = me.grid,
120613             view = grid.view;
120614             
120615         view.addElListener('mousewheel', me.cancelEdit, me);
120616         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
120617         me.mon(grid, {
120618             columnresize: me.cancelEdit,
120619             columnmove: me.cancelEdit,
120620             scope: me
120621         });
120622     },
120623
120624     /**
120625      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120626      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
120627      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
120628      */
120629     startEdit: function(record, columnHeader) {
120630         var me = this,
120631             value = record.get(columnHeader.dataIndex),
120632             context = me.getEditingContext(record, columnHeader),
120633             ed;
120634
120635         record = context.record;
120636         columnHeader = context.column;
120637
120638         // Complete the edit now, before getting the editor's target
120639         // cell DOM element. Completing the edit causes a view refresh.
120640         me.completeEdit();
120641
120642         context.originalValue = context.value = value;
120643         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120644             return false;
120645         }
120646         
120647         // See if the field is editable for the requested record
120648         if (columnHeader && !columnHeader.getEditor(record)) {
120649             return false;
120650         }
120651         
120652         ed = me.getEditor(record, columnHeader);
120653         if (ed) {
120654             me.context = context;
120655             me.setActiveEditor(ed);
120656             me.setActiveRecord(record);
120657             me.setActiveColumn(columnHeader);
120658
120659             // Defer, so we have some time between view scroll to sync up the editor
120660             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
120661         } else {
120662             // BrowserBug: WebKit & IE refuse to focus the element, rather
120663             // it will focus it and then immediately focus the body. This
120664             // temporary hack works for Webkit and IE6. IE7 and 8 are still
120665             // broken
120666             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
120667         }
120668     },
120669
120670     completeEdit: function() {
120671         var activeEd = this.getActiveEditor();
120672         if (activeEd) {
120673             activeEd.completeEdit();
120674         }
120675     },
120676
120677     // internal getters/setters
120678     setActiveEditor: function(ed) {
120679         this.activeEditor = ed;
120680     },
120681
120682     getActiveEditor: function() {
120683         return this.activeEditor;
120684     },
120685
120686     setActiveColumn: function(column) {
120687         this.activeColumn = column;
120688     },
120689
120690     getActiveColumn: function() {
120691         return this.activeColumn;
120692     },
120693
120694     setActiveRecord: function(record) {
120695         this.activeRecord = record;
120696     },
120697
120698     getActiveRecord: function() {
120699         return this.activeRecord;
120700     },
120701
120702     getEditor: function(record, column) {
120703         var me = this,
120704             editors = me.editors,
120705             editorId = column.getItemId(),
120706             editor = editors.getByKey(editorId);
120707
120708         if (editor) {
120709             return editor;
120710         } else {
120711             editor = column.getEditor(record);
120712             if (!editor) {
120713                 return false;
120714             }
120715
120716             // Allow them to specify a CellEditor in the Column
120717             if (!(editor instanceof Ext.grid.CellEditor)) {
120718                 editor = Ext.create('Ext.grid.CellEditor', {
120719                     editorId: editorId,
120720                     field: editor
120721                 });
120722             }
120723             editor.parentEl = me.grid.getEditorParent();
120724             // editor.parentEl should be set here.
120725             editor.on({
120726                 scope: me,
120727                 specialkey: me.onSpecialKey,
120728                 complete: me.onEditComplete,
120729                 canceledit: me.cancelEdit
120730             });
120731             editors.add(editor);
120732             return editor;
120733         }
120734     },
120735     
120736     // inherit docs
120737     setColumnField: function(column, field) {
120738         var ed = this.editors.getByKey(column.getItemId());
120739         Ext.destroy(ed, column.field);
120740         this.editors.removeAtKey(column.getItemId());
120741         this.callParent(arguments);
120742     },
120743
120744     /**
120745      * Gets the cell (td) for a particular record and column.
120746      * @param {Ext.data.Model} record
120747      * @param {Ext.grid.column.Column} column
120748      * @private
120749      */
120750     getCell: function(record, column) {
120751         return this.grid.getView().getCell(record, column);
120752     },
120753
120754     onSpecialKey: function(ed, field, e) {
120755         var grid = this.grid,
120756             sm;
120757         if (e.getKey() === e.TAB) {
120758             e.stopEvent();
120759             sm = grid.getSelectionModel();
120760             if (sm.onEditorTab) {
120761                 sm.onEditorTab(this, e);
120762             }
120763         }
120764     },
120765
120766     onEditComplete : function(ed, value, startValue) {
120767         var me = this,
120768             grid = me.grid,
120769             sm = grid.getSelectionModel(),
120770             activeColumn = me.getActiveColumn(),
120771             dataIndex;
120772
120773         if (activeColumn) {
120774             dataIndex = activeColumn.dataIndex;
120775
120776             me.setActiveEditor(null);
120777             me.setActiveColumn(null);
120778             me.setActiveRecord(null);
120779             delete sm.wasEditing;
120780     
120781             if (!me.validateEdit()) {
120782                 return;
120783             }
120784             // Only update the record if the new value is different than the
120785             // startValue, when the view refreshes its el will gain focus
120786             if (value !== startValue) {
120787                 me.context.record.set(dataIndex, value);
120788             // Restore focus back to the view's element.
120789             } else {
120790                 grid.getView().getEl(activeColumn).focus();
120791             }
120792             me.context.value = value;
120793             me.fireEvent('edit', me, me.context);
120794         }
120795     },
120796
120797     /**
120798      * Cancels any active editing.
120799      */
120800     cancelEdit: function() {
120801         var me = this,
120802             activeEd = me.getActiveEditor(),
120803             viewEl = me.grid.getView().getEl(me.getActiveColumn());
120804
120805         me.setActiveEditor(null);
120806         me.setActiveColumn(null);
120807         me.setActiveRecord(null);
120808         if (activeEd) {
120809             activeEd.cancelEdit();
120810             viewEl.focus();
120811         }
120812     },
120813
120814     /**
120815      * Starts editing by position (row/column)
120816      * @param {Object} position A position with keys of row and column.
120817      */
120818     startEditByPosition: function(position) {
120819         var me = this,
120820             grid = me.grid,
120821             sm = grid.getSelectionModel(),
120822             editRecord = grid.store.getAt(position.row),
120823             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
120824
120825         if (sm.selectByPosition) {
120826             sm.selectByPosition(position);
120827         }
120828         me.startEdit(editRecord, editColumnHeader);
120829     }
120830 });
120831 /**
120832  * This plugin provides drag and/or drop functionality for a GridView.
120833  *
120834  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
120835  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
120836  * methods with the following properties:
120837  *
120838  * - `copy` : Boolean
120839  *
120840  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
120841  *   the control key was pressed when the drag operation was begun.
120842  *
120843  * - `view` : GridView
120844  *
120845  *   The source GridView from which the drag originated.
120846  *
120847  * - `ddel` : HtmlElement
120848  *
120849  *   The drag proxy element which moves with the mouse
120850  *
120851  * - `item` : HtmlElement
120852  *
120853  *   The GridView node upon which the mousedown event was registered.
120854  *
120855  * - `records` : Array
120856  *
120857  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120858  *
120859  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
120860  * members of the same ddGroup which processes such data objects.
120861  *
120862  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
120863  * beforedrop}` and `{@link #drop drop}`
120864  *
120865  *     @example
120866  *     Ext.create('Ext.data.Store', {
120867  *         storeId:'simpsonsStore',
120868  *         fields:['name'],
120869  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
120870  *         proxy: {
120871  *             type: 'memory',
120872  *             reader: 'array'
120873  *         }
120874  *     });
120875  *
120876  *     Ext.create('Ext.grid.Panel', {
120877  *         store: 'simpsonsStore',
120878  *         columns: [
120879  *             {header: 'Name',  dataIndex: 'name', flex: true}
120880  *         ],
120881  *         viewConfig: {
120882  *             plugins: {
120883  *                 ptype: 'gridviewdragdrop',
120884  *                 dragText: 'Drag and drop to reorganize'
120885  *             }
120886  *         },
120887  *         height: 200,
120888  *         width: 400,
120889  *         renderTo: Ext.getBody()
120890  *     });
120891  */
120892 Ext.define('Ext.grid.plugin.DragDrop', {
120893     extend: 'Ext.AbstractPlugin',
120894     alias: 'plugin.gridviewdragdrop',
120895
120896     uses: [
120897         'Ext.view.DragZone',
120898         'Ext.grid.ViewDropZone'
120899     ],
120900
120901     /**
120902      * @event beforedrop
120903      * **This event is fired through the GridView. Add listeners to the GridView object**
120904      *
120905      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
120906      *
120907      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120908      *
120909      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
120910      * back to the point from which the drag began.
120911      *
120912      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
120913      * was valid, and that the repair operation should not take place.
120914      *
120915      * Any other return value continues with the data transfer operation.
120916      *
120917      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120918      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120919      *
120920      * - copy : Boolean
120921      *
120922      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120923      *   the control key was pressed when the drag operation was begun
120924      *
120925      * - view : GridView
120926      *
120927      *   The source GridView from which the drag originated.
120928      *
120929      * - ddel : HtmlElement
120930      *
120931      *   The drag proxy element which moves with the mouse
120932      *
120933      * - item : HtmlElement
120934      *
120935      *   The GridView node upon which the mousedown event was registered.
120936      *
120937      * - records : Array
120938      *
120939      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120940      *
120941      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
120942      *
120943      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
120944      * of the node.
120945      *
120946      * @param {Function} dropFunction
120947      *
120948      * A function to call to complete the data transfer operation and either move or copy Model instances from the
120949      * source View's Store to the destination View's Store.
120950      *
120951      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
120952      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
120953      *
120954      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
120955      */
120956
120957     /**
120958      * @event drop
120959      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
120960      * has been completed and the data has been moved or copied.
120961      *
120962      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120963      *
120964      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120965      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120966      *
120967      * - copy : Boolean
120968      *
120969      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120970      *   the control key was pressed when the drag operation was begun
120971      *
120972      * - view : GridView
120973      *
120974      *   The source GridView from which the drag originated.
120975      *
120976      * - ddel : HtmlElement
120977      *
120978      *   The drag proxy element which moves with the mouse
120979      *
120980      * - item : HtmlElement
120981      *
120982      *   The GridView node upon which the mousedown event was registered.
120983      *
120984      * - records : Array
120985      *
120986      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120987      *
120988      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
120989      *
120990      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
120991      * of the node.
120992      */
120993
120994     dragText : '{0} selected row{1}',
120995
120996     /**
120997      * @cfg {String} ddGroup
120998      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
120999      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
121000      */
121001     ddGroup : "GridDD",
121002
121003     /**
121004      * @cfg {String} dragGroup
121005      * The ddGroup to which the DragZone will belong.
121006      *
121007      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
121008      * Drag/DropZones which are members of the same ddGroup.
121009      */
121010
121011     /**
121012      * @cfg {String} dropGroup
121013      * The ddGroup to which the DropZone will belong.
121014      *
121015      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
121016      * Drag/DropZones which are members of the same ddGroup.
121017      */
121018
121019     /**
121020      * @cfg {Boolean} enableDrop
121021      * False to disallow the View from accepting drop gestures.
121022      */
121023     enableDrop: true,
121024
121025     /**
121026      * @cfg {Boolean} enableDrag
121027      * False to disallow dragging items from the View.
121028      */
121029     enableDrag: true,
121030
121031     init : function(view) {
121032         view.on('render', this.onViewRender, this, {single: true});
121033     },
121034
121035     /**
121036      * @private
121037      * AbstractComponent calls destroy on all its plugins at destroy time.
121038      */
121039     destroy: function() {
121040         Ext.destroy(this.dragZone, this.dropZone);
121041     },
121042
121043     enable: function() {
121044         var me = this;
121045         if (me.dragZone) {
121046             me.dragZone.unlock();
121047         }
121048         if (me.dropZone) {
121049             me.dropZone.unlock();
121050         }
121051         me.callParent();
121052     },
121053
121054     disable: function() {
121055         var me = this;
121056         if (me.dragZone) {
121057             me.dragZone.lock();
121058         }
121059         if (me.dropZone) {
121060             me.dropZone.lock();
121061         }
121062         me.callParent();
121063     },
121064
121065     onViewRender : function(view) {
121066         var me = this;
121067
121068         if (me.enableDrag) {
121069             me.dragZone = Ext.create('Ext.view.DragZone', {
121070                 view: view,
121071                 ddGroup: me.dragGroup || me.ddGroup,
121072                 dragText: me.dragText
121073             });
121074         }
121075
121076         if (me.enableDrop) {
121077             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
121078                 view: view,
121079                 ddGroup: me.dropGroup || me.ddGroup
121080             });
121081         }
121082     }
121083 });
121084 /**
121085  * @class Ext.grid.plugin.HeaderReorderer
121086  * @extends Ext.util.Observable
121087  * @private
121088  */
121089 Ext.define('Ext.grid.plugin.HeaderReorderer', {
121090     extend: 'Ext.util.Observable',
121091     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
121092     alias: 'plugin.gridheaderreorderer',
121093
121094     init: function(headerCt) {
121095         this.headerCt = headerCt;
121096         headerCt.on('render', this.onHeaderCtRender, this);
121097     },
121098
121099     /**
121100      * @private
121101      * AbstractComponent calls destroy on all its plugins at destroy time.
121102      */
121103     destroy: function() {
121104         Ext.destroy(this.dragZone, this.dropZone);
121105     },
121106
121107     onHeaderCtRender: function() {
121108         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
121109         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
121110         if (this.disabled) {
121111             this.dragZone.disable();
121112         }
121113     },
121114     
121115     enable: function() {
121116         this.disabled = false;
121117         if (this.dragZone) {
121118             this.dragZone.enable();
121119         }
121120     },
121121     
121122     disable: function() {
121123         this.disabled = true;
121124         if (this.dragZone) {
121125             this.dragZone.disable();
121126         }
121127     }
121128 });
121129 /**
121130  * @class Ext.grid.plugin.HeaderResizer
121131  * @extends Ext.util.Observable
121132  *
121133  * Plugin to add header resizing functionality to a HeaderContainer.
121134  * Always resizing header to the left of the splitter you are resizing.
121135  */
121136 Ext.define('Ext.grid.plugin.HeaderResizer', {
121137     extend: 'Ext.util.Observable',
121138     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
121139     alias: 'plugin.gridheaderresizer',
121140
121141     disabled: false,
121142
121143     /**
121144      * @cfg {Boolean} dynamic
121145      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
121146      */
121147     configs: {
121148         dynamic: true
121149     },
121150
121151     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
121152
121153     minColWidth: 40,
121154     maxColWidth: 1000,
121155     wResizeCursor: 'col-resize',
121156     eResizeCursor: 'col-resize',
121157     // not using w and e resize bc we are only ever resizing one
121158     // column
121159     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
121160     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
121161
121162     init: function(headerCt) {
121163         this.headerCt = headerCt;
121164         headerCt.on('render', this.afterHeaderRender, this, {single: true});
121165     },
121166
121167     /**
121168      * @private
121169      * AbstractComponent calls destroy on all its plugins at destroy time.
121170      */
121171     destroy: function() {
121172         if (this.tracker) {
121173             this.tracker.destroy();
121174         }
121175     },
121176
121177     afterHeaderRender: function() {
121178         var headerCt = this.headerCt,
121179             el = headerCt.el;
121180
121181         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
121182
121183         this.tracker = Ext.create('Ext.dd.DragTracker', {
121184             disabled: this.disabled,
121185             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
121186             onStart: Ext.Function.bind(this.onStart, this),
121187             onDrag: Ext.Function.bind(this.onDrag, this),
121188             onEnd: Ext.Function.bind(this.onEnd, this),
121189             tolerance: 3,
121190             autoStart: 300,
121191             el: el
121192         });
121193     },
121194
121195     // As we mouse over individual headers, change the cursor to indicate
121196     // that resizing is available, and cache the resize target header for use
121197     // if/when they mousedown.
121198     onHeaderCtMouseMove: function(e, t) {
121199         if (this.headerCt.dragging) {
121200             if (this.activeHd) {
121201                 this.activeHd.el.dom.style.cursor = '';
121202                 delete this.activeHd;
121203             }
121204         } else {
121205             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
121206                 overHeader, resizeHeader;
121207
121208             if (headerEl){
121209                 overHeader = Ext.getCmp(headerEl.id);
121210
121211                 // On left edge, go back to the previous non-hidden header.
121212                 if (overHeader.isOnLeftEdge(e)) {
121213                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
121214
121215                 }
121216                 // Else, if on the right edge, we're resizing the column we are over
121217                 else if (overHeader.isOnRightEdge(e)) {
121218                     resizeHeader = overHeader;
121219                 }
121220                 // Between the edges: we are not resizing
121221                 else {
121222                     resizeHeader = null;
121223                 }
121224
121225                 // We *are* resizing
121226                 if (resizeHeader) {
121227                     // If we're attempting to resize a group header, that cannot be resized,
121228                     // so find its last visible leaf header; Group headers are sized
121229                     // by the size of their child headers.
121230                     if (resizeHeader.isGroupHeader) {
121231                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
121232                     }
121233
121234                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
121235                     // check whether the resizablwe property is set to false.
121236                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
121237                         this.activeHd = resizeHeader;
121238                         overHeader.el.dom.style.cursor = this.eResizeCursor;
121239                     }
121240                 // reset
121241                 } else {
121242                     overHeader.el.dom.style.cursor = '';
121243                     delete this.activeHd;
121244                 }
121245             }
121246         }
121247     },
121248
121249     // only start when there is an activeHd
121250     onBeforeStart : function(e){
121251         var t = e.getTarget();
121252         // cache the activeHd because it will be cleared.
121253         this.dragHd = this.activeHd;
121254
121255         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
121256             //this.headerCt.dragging = true;
121257             this.tracker.constrainTo = this.getConstrainRegion();
121258             return true;
121259         } else {
121260             this.headerCt.dragging = false;
121261             return false;
121262         }
121263     },
121264
121265     // get the region to constrain to, takes into account max and min col widths
121266     getConstrainRegion: function() {
121267         var dragHdEl = this.dragHd.el,
121268             region   = Ext.util.Region.getRegion(dragHdEl);
121269
121270         return region.adjust(
121271             0,
121272             this.maxColWidth - dragHdEl.getWidth(),
121273             0,
121274             this.minColWidth
121275         );
121276     },
121277
121278     // initialize the left and right hand side markers around
121279     // the header that we are resizing
121280     onStart: function(e){
121281         var me       = this,
121282             dragHd   = me.dragHd,
121283             dragHdEl = dragHd.el,
121284             width    = dragHdEl.getWidth(),
121285             headerCt = me.headerCt,
121286             t        = e.getTarget();
121287
121288         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
121289             headerCt.dragging = true;
121290         }
121291
121292         me.origWidth = width;
121293
121294         // setup marker proxies
121295         if (!me.dynamic) {
121296             var xy           = dragHdEl.getXY(),
121297                 gridSection  = headerCt.up('[scrollerOwner]'),
121298                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
121299                 firstSection = dragHct.up(),
121300                 lhsMarker    = gridSection.getLhsMarker(),
121301                 rhsMarker    = gridSection.getRhsMarker(),
121302                 el           = rhsMarker.parent(),
121303                 offsetLeft   = el.getLeft(true),
121304                 offsetTop    = el.getTop(true),
121305                 topLeft      = el.translatePoints(xy),
121306                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
121307                 top = topLeft.top - offsetTop;
121308
121309             lhsMarker.setTop(top);
121310             rhsMarker.setTop(top);
121311             lhsMarker.setHeight(markerHeight);
121312             rhsMarker.setHeight(markerHeight);
121313             lhsMarker.setLeft(topLeft.left - offsetLeft);
121314             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
121315         }
121316     },
121317
121318     // synchronize the rhsMarker with the mouse movement
121319     onDrag: function(e){
121320         if (!this.dynamic) {
121321             var xy          = this.tracker.getXY('point'),
121322                 gridSection = this.headerCt.up('[scrollerOwner]'),
121323                 rhsMarker   = gridSection.getRhsMarker(),
121324                 el          = rhsMarker.parent(),
121325                 topLeft     = el.translatePoints(xy),
121326                 offsetLeft  = el.getLeft(true);
121327
121328             rhsMarker.setLeft(topLeft.left - offsetLeft);
121329         // Resize as user interacts
121330         } else {
121331             this.doResize();
121332         }
121333     },
121334
121335     onEnd: function(e){
121336         this.headerCt.dragging = false;
121337         if (this.dragHd) {
121338             if (!this.dynamic) {
121339                 var dragHd      = this.dragHd,
121340                     gridSection = this.headerCt.up('[scrollerOwner]'),
121341                     lhsMarker   = gridSection.getLhsMarker(),
121342                     rhsMarker   = gridSection.getRhsMarker(),
121343                     currWidth   = dragHd.getWidth(),
121344                     offset      = this.tracker.getOffset('point'),
121345                     offscreen   = -9999;
121346
121347                 // hide markers
121348                 lhsMarker.setLeft(offscreen);
121349                 rhsMarker.setLeft(offscreen);
121350             }
121351             this.doResize();
121352         }
121353     },
121354
121355     doResize: function() {
121356         if (this.dragHd) {
121357             var dragHd = this.dragHd,
121358                 nextHd,
121359                 offset = this.tracker.getOffset('point');
121360
121361             // resize the dragHd
121362             if (dragHd.flex) {
121363                 delete dragHd.flex;
121364             }
121365
121366             this.headerCt.suspendLayout = true;
121367             dragHd.setWidth(this.origWidth + offset[0], false);
121368
121369             // In the case of forceFit, change the following Header width.
121370             // Then apply the two width changes by laying out the owning HeaderContainer
121371             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
121372             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
121373             if (this.headerCt.forceFit) {
121374                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
121375                 if (nextHd) {
121376                     delete nextHd.flex;
121377                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
121378                 }
121379             }
121380             this.headerCt.suspendLayout = false;
121381             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
121382         }
121383     },
121384
121385     disable: function() {
121386         this.disabled = true;
121387         if (this.tracker) {
121388             this.tracker.disable();
121389         }
121390     },
121391
121392     enable: function() {
121393         this.disabled = false;
121394         if (this.tracker) {
121395             this.tracker.enable();
121396         }
121397     }
121398 });
121399 /**
121400  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
121401  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
121402  * for editing. There is a button to save or cancel all changes for the edit.
121403  *
121404  * The field that will be used for the editor is defined at the
121405  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
121406  * If an editor is not specified for a particular column then that column won't be editable and the value of
121407  * the column will be displayed.
121408  *
121409  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
121410  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
121411  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
121412  *
121413  *     @example
121414  *     Ext.create('Ext.data.Store', {
121415  *         storeId:'simpsonsStore',
121416  *         fields:['name', 'email', 'phone'],
121417  *         data: [
121418  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
121419  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
121420  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
121421  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
121422  *         ]
121423  *     });
121424  *
121425  *     Ext.create('Ext.grid.Panel', {
121426  *         title: 'Simpsons',
121427  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
121428  *         columns: [
121429  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
121430  *             {header: 'Email', dataIndex: 'email', flex:1,
121431  *                 editor: {
121432  *                     xtype: 'textfield',
121433  *                     allowBlank: false
121434  *                 }
121435  *             },
121436  *             {header: 'Phone', dataIndex: 'phone'}
121437  *         ],
121438  *         selType: 'rowmodel',
121439  *         plugins: [
121440  *             Ext.create('Ext.grid.plugin.RowEditing', {
121441  *                 clicksToEdit: 1
121442  *             })
121443  *         ],
121444  *         height: 200,
121445  *         width: 400,
121446  *         renderTo: Ext.getBody()
121447  *     });
121448  */
121449 Ext.define('Ext.grid.plugin.RowEditing', {
121450     extend: 'Ext.grid.plugin.Editing',
121451     alias: 'plugin.rowediting',
121452
121453     requires: [
121454         'Ext.grid.RowEditor'
121455     ],
121456
121457     editStyle: 'row',
121458
121459     /**
121460      * @cfg {Boolean} autoCancel
121461      * True to automatically cancel any pending changes when the row editor begins editing a new row.
121462      * False to force the user to explicitly cancel the pending changes. Defaults to true.
121463      */
121464     autoCancel: true,
121465
121466     /**
121467      * @cfg {Number} clicksToMoveEditor
121468      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
121469      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
121470      */
121471
121472     /**
121473      * @cfg {Boolean} errorSummary
121474      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
121475      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
121476      */
121477     errorSummary: true,
121478
121479     /**
121480      * @event beforeedit
121481      * Fires before row editing is triggered.
121482      *
121483      * @param {Ext.grid.plugin.Editing} editor
121484      * @param {Object} e An edit event with the following properties:
121485      *
121486      * - grid - The grid this editor is on
121487      * - view - The grid view
121488      * - store - The grid store
121489      * - record - The record being edited
121490      * - row - The grid table row
121491      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121492      * - rowIdx - The row index that is being edited
121493      * - colIdx - The column index that initiated the edit
121494      * - cancel - Set this to true to cancel the edit or return false from your handler.
121495      */
121496     
121497     /**
121498      * @event canceledit
121499      * Fires when the user has started editing a row but then cancelled the edit
121500      * @param {Object} grid The grid
121501      */
121502     
121503     /**
121504      * @event edit
121505      * Fires after a row is edited. Usage example:
121506      *
121507      *     grid.on('edit', function(editor, e) {
121508      *         // commit the changes right after editing finished
121509      *         e.record.commit();
121510      *     };
121511      *
121512      * @param {Ext.grid.plugin.Editing} editor
121513      * @param {Object} e An edit event with the following properties:
121514      *
121515      * - grid - The grid this editor is on
121516      * - view - The grid view
121517      * - store - The grid store
121518      * - record - The record being edited
121519      * - row - The grid table row
121520      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121521      * - rowIdx - The row index that is being edited
121522      * - colIdx - The column index that initiated the edit
121523      */
121524     /**
121525      * @event validateedit
121526      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
121527      * edit event object has the following properties
121528      *
121529      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
121530      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
121531      * and then setting the field's new value in the Record directly:
121532      *
121533      *     grid.on('validateedit', function(editor, e) {
121534      *       var myTargetRow = 6;
121535      *
121536      *       if (e.rowIdx == myTargetRow) {
121537      *         e.cancel = true;
121538      *         e.record.data[e.field] = e.value;
121539      *       }
121540      *     });
121541      *
121542      * @param {Ext.grid.plugin.Editing} editor
121543      * @param {Object} e An edit event with the following properties:
121544      *
121545      * - grid - The grid this editor is on
121546      * - view - The grid view
121547      * - store - The grid store
121548      * - record - The record being edited
121549      * - row - The grid table row
121550      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121551      * - rowIdx - The row index that is being edited
121552      * - colIdx - The column index that initiated the edit
121553      * - cancel - Set this to true to cancel the edit or return false from your handler.
121554      */
121555
121556     constructor: function() {
121557         var me = this;
121558         me.callParent(arguments);
121559
121560         if (!me.clicksToMoveEditor) {
121561             me.clicksToMoveEditor = me.clicksToEdit;
121562         }
121563
121564         me.autoCancel = !!me.autoCancel;
121565     },
121566
121567     /**
121568      * @private
121569      * AbstractComponent calls destroy on all its plugins at destroy time.
121570      */
121571     destroy: function() {
121572         var me = this;
121573         Ext.destroy(me.editor);
121574         me.callParent(arguments);
121575     },
121576
121577     /**
121578      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121579      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
121580      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
121581      */
121582     startEdit: function(record, columnHeader) {
121583         var me = this,
121584             editor = me.getEditor();
121585
121586         if (me.callParent(arguments) === false) {
121587             return false;
121588         }
121589
121590         // Fire off our editor
121591         if (editor.beforeEdit() !== false) {
121592             editor.startEdit(me.context.record, me.context.column);
121593         }
121594     },
121595
121596     // private
121597     cancelEdit: function() {
121598         var me = this;
121599
121600         if (me.editing) {
121601             me.getEditor().cancelEdit();
121602             me.callParent(arguments);
121603             
121604             me.fireEvent('canceledit', me.context);
121605         }
121606     },
121607
121608     // private
121609     completeEdit: function() {
121610         var me = this;
121611
121612         if (me.editing && me.validateEdit()) {
121613             me.editing = false;
121614             me.fireEvent('edit', me.context);
121615         }
121616     },
121617
121618     // private
121619     validateEdit: function() {
121620         var me             = this,
121621             editor         = me.editor,
121622             context        = me.context,
121623             record         = context.record,
121624             newValues      = {},
121625             originalValues = {},
121626             name;
121627
121628         editor.items.each(function(item) {
121629             name = item.name;
121630
121631             newValues[name]      = item.getValue();
121632             originalValues[name] = record.get(name);
121633         });
121634
121635         Ext.apply(context, {
121636             newValues      : newValues,
121637             originalValues : originalValues
121638         });
121639
121640         return me.callParent(arguments) && me.getEditor().completeEdit();
121641     },
121642
121643     // private
121644     getEditor: function() {
121645         var me = this;
121646
121647         if (!me.editor) {
121648             me.editor = me.initEditor();
121649         }
121650         return me.editor;
121651     },
121652
121653     // private
121654     initEditor: function() {
121655         var me = this,
121656             grid = me.grid,
121657             view = me.view,
121658             headerCt = grid.headerCt;
121659
121660         return Ext.create('Ext.grid.RowEditor', {
121661             autoCancel: me.autoCancel,
121662             errorSummary: me.errorSummary,
121663             fields: headerCt.getGridColumns(),
121664             hidden: true,
121665
121666             // keep a reference..
121667             editingPlugin: me,
121668             renderTo: view.el
121669         });
121670     },
121671
121672     // private
121673     initEditTriggers: function() {
121674         var me = this,
121675             grid = me.grid,
121676             view = me.view,
121677             headerCt = grid.headerCt,
121678             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
121679
121680         me.callParent(arguments);
121681
121682         if (me.clicksToMoveEditor !== me.clicksToEdit) {
121683             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
121684         }
121685
121686         view.on('render', function() {
121687             // Column events
121688             me.mon(headerCt, {
121689                 add: me.onColumnAdd,
121690                 remove: me.onColumnRemove,
121691                 columnresize: me.onColumnResize,
121692                 columnhide: me.onColumnHide,
121693                 columnshow: me.onColumnShow,
121694                 columnmove: me.onColumnMove,
121695                 scope: me
121696             });
121697         }, me, { single: true });
121698     },
121699
121700     startEditByClick: function() {
121701         var me = this;
121702         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
121703             me.callParent(arguments);
121704         }
121705     },
121706
121707     moveEditorByClick: function() {
121708         var me = this;
121709         if (me.editing) {
121710             me.superclass.startEditByClick.apply(me, arguments);
121711         }
121712     },
121713
121714     // private
121715     onColumnAdd: function(ct, column) {
121716         if (column.isHeader) {
121717             var me = this,
121718                 editor;
121719
121720             me.initFieldAccessors(column);
121721             editor = me.getEditor();
121722
121723             if (editor && editor.onColumnAdd) {
121724                 editor.onColumnAdd(column);
121725             }
121726         }
121727     },
121728
121729     // private
121730     onColumnRemove: function(ct, column) {
121731         if (column.isHeader) {
121732             var me = this,
121733                 editor = me.getEditor();
121734
121735             if (editor && editor.onColumnRemove) {
121736                 editor.onColumnRemove(column);
121737             }
121738             me.removeFieldAccessors(column);
121739         }
121740     },
121741
121742     // private
121743     onColumnResize: function(ct, column, width) {
121744         if (column.isHeader) {
121745             var me = this,
121746                 editor = me.getEditor();
121747
121748             if (editor && editor.onColumnResize) {
121749                 editor.onColumnResize(column, width);
121750             }
121751         }
121752     },
121753
121754     // private
121755     onColumnHide: function(ct, column) {
121756         // no isHeader check here since its already a columnhide event.
121757         var me = this,
121758             editor = me.getEditor();
121759
121760         if (editor && editor.onColumnHide) {
121761             editor.onColumnHide(column);
121762         }
121763     },
121764
121765     // private
121766     onColumnShow: function(ct, column) {
121767         // no isHeader check here since its already a columnshow event.
121768         var me = this,
121769             editor = me.getEditor();
121770
121771         if (editor && editor.onColumnShow) {
121772             editor.onColumnShow(column);
121773         }
121774     },
121775
121776     // private
121777     onColumnMove: function(ct, column, fromIdx, toIdx) {
121778         // no isHeader check here since its already a columnmove event.
121779         var me = this,
121780             editor = me.getEditor();
121781
121782         if (editor && editor.onColumnMove) {
121783             editor.onColumnMove(column, fromIdx, toIdx);
121784         }
121785     },
121786
121787     // private
121788     setColumnField: function(column, field) {
121789         var me = this;
121790         me.callParent(arguments);
121791         me.getEditor().setField(column.field, column);
121792     }
121793 });
121794
121795 /**
121796  * @class Ext.grid.property.Grid
121797  * @extends Ext.grid.Panel
121798  *
121799  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
121800  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
121801  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
121802  *
121803  *     @example
121804  *     Ext.create('Ext.grid.property.Grid', {
121805  *         title: 'Properties Grid',
121806  *         width: 300,
121807  *         renderTo: Ext.getBody(),
121808  *         source: {
121809  *             "(name)": "My Object",
121810  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
121811  *             "Available": false,
121812  *             "Version": .01,
121813  *             "Description": "A test object"
121814  *         }
121815  *     });
121816  */
121817 Ext.define('Ext.grid.property.Grid', {
121818
121819     extend: 'Ext.grid.Panel',
121820
121821     alias: 'widget.propertygrid',
121822
121823     alternateClassName: 'Ext.grid.PropertyGrid',
121824
121825     uses: [
121826        'Ext.grid.plugin.CellEditing',
121827        'Ext.grid.property.Store',
121828        'Ext.grid.property.HeaderContainer',
121829        'Ext.XTemplate',
121830        'Ext.grid.CellEditor',
121831        'Ext.form.field.Date',
121832        'Ext.form.field.Text',
121833        'Ext.form.field.Number'
121834     ],
121835
121836    /**
121837     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
121838     * If specified, the display name will be shown in the name column instead of the property name.
121839     */
121840
121841     /**
121842     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121843     */
121844
121845     /**
121846     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
121847     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
121848     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121849     * associated with a custom input control by specifying a custom editor.  The name of the editor
121850     * type should correspond with the name of the property that will use the editor.  Example usage:
121851     * <pre><code>
121852 var grid = new Ext.grid.property.Grid({
121853
121854     // Custom editors for certain property names
121855     customEditors: {
121856         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
121857     },
121858
121859     // Displayed name for property names in the source
121860     propertyNames: {
121861         evtStart: 'Start Time'
121862     },
121863
121864     // Data object containing properties to edit
121865     source: {
121866         evtStart: '10:00 AM'
121867     }
121868 });
121869 </code></pre>
121870     */
121871
121872     /**
121873     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121874     */
121875
121876     /**
121877     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
121878     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
121879     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121880     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
121881     * that it will render.  Example usage:
121882     * <pre><code>
121883 var grid = Ext.create('Ext.grid.property.Grid', {
121884     customRenderers: {
121885         Available: function(v){
121886             if (v) {
121887                 return '<span style="color: green;">Yes</span>';
121888             } else {
121889                 return '<span style="color: red;">No</span>';
121890             }
121891         }
121892     },
121893     source: {
121894         Available: true
121895     }
121896 });
121897 </code></pre>
121898     */
121899
121900     /**
121901      * @cfg {String} valueField
121902      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
121903      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121904      */
121905     valueField: 'value',
121906
121907     /**
121908      * @cfg {String} nameField
121909      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
121910      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121911      */
121912     nameField: 'name',
121913
121914     /**
121915      * @cfg {Number} nameColumnWidth
121916      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
121917      */
121918
121919     // private config overrides
121920     enableColumnMove: false,
121921     columnLines: true,
121922     stripeRows: false,
121923     trackMouseOver: false,
121924     clicksToEdit: 1,
121925     enableHdMenu: false,
121926
121927     // private
121928     initComponent : function(){
121929         var me = this;
121930
121931         me.addCls(Ext.baseCSSPrefix + 'property-grid');
121932         me.plugins = me.plugins || [];
121933
121934         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
121935         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
121936             clicksToEdit: me.clicksToEdit,
121937
121938             // Inject a startEdit which always edits the value column
121939             startEdit: function(record, column) {
121940                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
121941                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
121942             }
121943         }));
121944
121945         me.selModel = {
121946             selType: 'cellmodel',
121947             onCellSelect: function(position) {
121948                 if (position.column != 1) {
121949                     position.column = 1;
121950                 }
121951                 return this.self.prototype.onCellSelect.call(this, position);
121952             }
121953         };
121954         me.customRenderers = me.customRenderers || {};
121955         me.customEditors = me.customEditors || {};
121956
121957         // Create a property.Store from the source object unless configured with a store
121958         if (!me.store) {
121959             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
121960         }
121961
121962         me.store.sort('name', 'ASC');
121963         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
121964
121965         me.addEvents(
121966             /**
121967              * @event beforepropertychange
121968              * Fires before a property value changes.  Handlers can return false to cancel the property change
121969              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
121970              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
121971              * as the {@link #source} config property).
121972              * @param {String} recordId The record's id in the data store
121973              * @param {Object} value The current edited property value
121974              * @param {Object} oldValue The original property value prior to editing
121975              */
121976             'beforepropertychange',
121977             /**
121978              * @event propertychange
121979              * Fires after a property value has changed.
121980              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
121981              * as the {@link #source} config property).
121982              * @param {String} recordId The record's id in the data store
121983              * @param {Object} value The current edited property value
121984              * @param {Object} oldValue The original property value prior to editing
121985              */
121986             'propertychange'
121987         );
121988         me.callParent();
121989
121990         // Inject a custom implementation of walkCells which only goes up or down
121991         me.getView().walkCells = this.walkCells;
121992
121993         // Set up our default editor set for the 4 atomic data types
121994         me.editors = {
121995             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
121996             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
121997             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
121998             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
121999                 editable: false,
122000                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
122001             })})
122002         };
122003
122004         // Track changes to the data so we can fire our events.
122005         me.store.on('update', me.onUpdate, me);
122006     },
122007
122008     // private
122009     onUpdate : function(store, record, operation) {
122010         var me = this,
122011             v, oldValue;
122012
122013         if (operation == Ext.data.Model.EDIT) {
122014             v = record.get(me.valueField);
122015             oldValue = record.modified.value;
122016             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
122017                 if (me.source) {
122018                     me.source[record.getId()] = v;
122019                 }
122020                 record.commit();
122021                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
122022             } else {
122023                 record.reject();
122024             }
122025         }
122026     },
122027
122028     // Custom implementation of walkCells which only goes up and down.
122029     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
122030         if (direction == 'left') {
122031             direction = 'up';
122032         } else if (direction == 'right') {
122033             direction = 'down';
122034         }
122035         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
122036         if (!pos.column) {
122037             pos.column = 1;
122038         }
122039         return pos;
122040     },
122041
122042     // private
122043     // returns the correct editor type for the property type, or a custom one keyed by the property name
122044     getCellEditor : function(record, column) {
122045         var me = this,
122046             propName = record.get(me.nameField),
122047             val = record.get(me.valueField),
122048             editor = me.customEditors[propName];
122049
122050         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
122051         // If it's not even a Field, just a config object, instantiate it before wrapping it.
122052         if (editor) {
122053             if (!(editor instanceof Ext.grid.CellEditor)) {
122054                 if (!(editor instanceof Ext.form.field.Base)) {
122055                     editor = Ext.ComponentManager.create(editor, 'textfield');
122056                 }
122057                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
122058             }
122059         } else if (Ext.isDate(val)) {
122060             editor = me.editors.date;
122061         } else if (Ext.isNumber(val)) {
122062             editor = me.editors.number;
122063         } else if (Ext.isBoolean(val)) {
122064             editor = me.editors['boolean'];
122065         } else {
122066             editor = me.editors.string;
122067         }
122068
122069         // Give the editor a unique ID because the CellEditing plugin caches them
122070         editor.editorId = propName;
122071         return editor;
122072     },
122073
122074     beforeDestroy: function() {
122075         var me = this;
122076         me.callParent();
122077         me.destroyEditors(me.editors);
122078         me.destroyEditors(me.customEditors);
122079         delete me.source;
122080     },
122081
122082     destroyEditors: function (editors) {
122083         for (var ed in editors) {
122084             if (editors.hasOwnProperty(ed)) {
122085                 Ext.destroy(editors[ed]);
122086             }
122087         }
122088     },
122089
122090     /**
122091      * Sets the source data object containing the property data.  The data object can contain one or more name/value
122092      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
122093      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
122094      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
122095      * existing data.  See also the {@link #source} config value.  Example usage:
122096      * <pre><code>
122097 grid.setSource({
122098     "(name)": "My Object",
122099     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
122100     "Available": false,  // boolean type
122101     "Version": .01,      // decimal type
122102     "Description": "A test object"
122103 });
122104 </code></pre>
122105      * @param {Object} source The data object
122106      */
122107     setSource: function(source) {
122108         this.source = source;
122109         this.propStore.setSource(source);
122110     },
122111
122112     /**
122113      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
122114      * format of the data object.
122115      * @return {Object} The data object
122116      */
122117     getSource: function() {
122118         return this.propStore.getSource();
122119     },
122120
122121     /**
122122      * Sets the value of a property.
122123      * @param {String} prop The name of the property to set
122124      * @param {Object} value The value to test
122125      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
122126      */
122127     setProperty: function(prop, value, create) {
122128         this.propStore.setValue(prop, value, create);
122129     },
122130
122131     /**
122132      * Removes a property from the grid.
122133      * @param {String} prop The name of the property to remove
122134      */
122135     removeProperty: function(prop) {
122136         this.propStore.remove(prop);
122137     }
122138
122139     /**
122140      * @cfg store
122141      * @hide
122142      */
122143     /**
122144      * @cfg colModel
122145      * @hide
122146      */
122147     /**
122148      * @cfg cm
122149      * @hide
122150      */
122151     /**
122152      * @cfg columns
122153      * @hide
122154      */
122155 });
122156 /**
122157  * @class Ext.grid.property.HeaderContainer
122158  * @extends Ext.grid.header.Container
122159  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
122160  */
122161 Ext.define('Ext.grid.property.HeaderContainer', {
122162
122163     extend: 'Ext.grid.header.Container',
122164
122165     alternateClassName: 'Ext.grid.PropertyColumnModel',
122166     
122167     nameWidth: 115,
122168
122169     // private - strings used for locale support
122170     nameText : 'Name',
122171     valueText : 'Value',
122172     dateFormat : 'm/j/Y',
122173     trueText: 'true',
122174     falseText: 'false',
122175
122176     // private
122177     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
122178
122179     /**
122180      * Creates new HeaderContainer.
122181      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
122182      * @param {Object} source The source data config object
122183      */
122184     constructor : function(grid, store) {
122185         var me = this;
122186         
122187         me.grid = grid;
122188         me.store = store;
122189         me.callParent([{
122190             items: [{
122191                 header: me.nameText,
122192                 width: grid.nameColumnWidth || me.nameWidth,
122193                 sortable: true,
122194                 dataIndex: grid.nameField,
122195                 renderer: Ext.Function.bind(me.renderProp, me),
122196                 itemId: grid.nameField,
122197                 menuDisabled :true,
122198                 tdCls: me.nameColumnCls
122199             }, {
122200                 header: me.valueText,
122201                 renderer: Ext.Function.bind(me.renderCell, me),
122202                 getEditor: Ext.Function.bind(me.getCellEditor, me),
122203                 flex: 1,
122204                 fixed: true,
122205                 dataIndex: grid.valueField,
122206                 itemId: grid.valueField,
122207                 menuDisabled: true
122208             }]
122209         }]);
122210     },
122211     
122212     getCellEditor: function(record){
122213         return this.grid.getCellEditor(record, this);
122214     },
122215
122216     // private
122217     // Render a property name cell
122218     renderProp : function(v) {
122219         return this.getPropertyName(v);
122220     },
122221
122222     // private
122223     // Render a property value cell
122224     renderCell : function(val, meta, rec) {
122225         var me = this,
122226             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
122227             result = val;
122228
122229         if (renderer) {
122230             return renderer.apply(me, arguments);
122231         }
122232         if (Ext.isDate(val)) {
122233             result = me.renderDate(val);
122234         } else if (Ext.isBoolean(val)) {
122235             result = me.renderBool(val);
122236         }
122237         return Ext.util.Format.htmlEncode(result);
122238     },
122239
122240     // private
122241     renderDate : Ext.util.Format.date,
122242
122243     // private
122244     renderBool : function(bVal) {
122245         return this[bVal ? 'trueText' : 'falseText'];
122246     },
122247
122248     // private
122249     // Renders custom property names instead of raw names if defined in the Grid
122250     getPropertyName : function(name) {
122251         var pn = this.grid.propertyNames;
122252         return pn && pn[name] ? pn[name] : name;
122253     }
122254 });
122255 /**
122256  * @class Ext.grid.property.Property
122257  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
122258  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
122259  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
122260  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
122261  * can also be created explicitly as shown below.  Example usage:
122262  * <pre><code>
122263 var rec = new Ext.grid.property.Property({
122264     name: 'birthday',
122265     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
122266 });
122267 // Add record to an already populated grid
122268 grid.store.addSorted(rec);
122269 </code></pre>
122270  * @constructor
122271  * @param {Object} config A data object in the format:<pre><code>
122272 {
122273     name: [name],
122274     value: [value]
122275 }</code></pre>
122276  * The specified value's type
122277  * will be read automatically by the grid to determine the type of editor to use when displaying it.
122278  */
122279 Ext.define('Ext.grid.property.Property', {
122280     extend: 'Ext.data.Model',
122281
122282     alternateClassName: 'Ext.PropGridProperty',
122283
122284     fields: [{
122285         name: 'name',
122286         type: 'string'
122287     }, {
122288         name: 'value'
122289     }],
122290     idProperty: 'name'
122291 });
122292 /**
122293  * @class Ext.grid.property.Store
122294  * @extends Ext.data.Store
122295  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
122296  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
122297  * used by the {@link Ext.data.Store} base class.
122298  */
122299 Ext.define('Ext.grid.property.Store', {
122300
122301     extend: 'Ext.data.Store',
122302
122303     alternateClassName: 'Ext.grid.PropertyStore',
122304
122305     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
122306
122307     /**
122308      * Creates new property store.
122309      * @param {Ext.grid.Panel} grid The grid this store will be bound to
122310      * @param {Object} source The source data config object
122311      */
122312     constructor : function(grid, source){
122313         var me = this;
122314         
122315         me.grid = grid;
122316         me.source = source;
122317         me.callParent([{
122318             data: source,
122319             model: Ext.grid.property.Property,
122320             proxy: me.getProxy()
122321         }]);
122322     },
122323
122324     // Return a singleton, customized Proxy object which configures itself with a custom Reader
122325     getProxy: function() {
122326         if (!this.proxy) {
122327             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
122328                 model: Ext.grid.property.Property,
122329                 reader: this.getReader()
122330             });
122331         }
122332         return this.proxy;
122333     },
122334
122335     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
122336     getReader: function() {
122337         if (!this.reader) {
122338             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
122339                 model: Ext.grid.property.Property,
122340
122341                 buildExtractors: Ext.emptyFn,
122342
122343                 read: function(dataObject) {
122344                     return this.readRecords(dataObject);
122345                 },
122346
122347                 readRecords: function(dataObject) {
122348                     var val,
122349                         propName,
122350                         result = {
122351                             records: [],
122352                             success: true
122353                         };
122354
122355                     for (propName in dataObject) {
122356                         if (dataObject.hasOwnProperty(propName)) {
122357                             val = dataObject[propName];
122358                             if (this.isEditableValue(val)) {
122359                                 result.records.push(new Ext.grid.property.Property({
122360                                     name: propName,
122361                                     value: val
122362                                 }, propName));
122363                             }
122364                         }
122365                     }
122366                     result.total = result.count = result.records.length;
122367                     return Ext.create('Ext.data.ResultSet', result);
122368                 },
122369
122370                 // private
122371                 isEditableValue: function(val){
122372                     return Ext.isPrimitive(val) || Ext.isDate(val);
122373                 }
122374             });
122375         }
122376         return this.reader;
122377     },
122378
122379     // protected - should only be called by the grid.  Use grid.setSource instead.
122380     setSource : function(dataObject) {
122381         var me = this;
122382
122383         me.source = dataObject;
122384         me.suspendEvents();
122385         me.removeAll();
122386         me.proxy.data = dataObject;
122387         me.load();
122388         me.resumeEvents();
122389         me.fireEvent('datachanged', me);
122390     },
122391
122392     // private
122393     getProperty : function(row) {
122394        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
122395     },
122396
122397     // private
122398     setValue : function(prop, value, create){
122399         var me = this,
122400             rec = me.getRec(prop);
122401             
122402         if (rec) {
122403             rec.set('value', value);
122404             me.source[prop] = value;
122405         } else if (create) {
122406             // only create if specified.
122407             me.source[prop] = value;
122408             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
122409             me.add(rec);
122410         }
122411     },
122412
122413     // private
122414     remove : function(prop) {
122415         var rec = this.getRec(prop);
122416         if (rec) {
122417             this.callParent([rec]);
122418             delete this.source[prop];
122419         }
122420     },
122421
122422     // private
122423     getRec : function(prop) {
122424         return this.getById(prop);
122425     },
122426
122427     // protected - should only be called by the grid.  Use grid.getSource instead.
122428     getSource : function() {
122429         return this.source;
122430     }
122431 });
122432 /**
122433  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
122434  * Component size.
122435  * @class Ext.layout.component.Body
122436  * @extends Ext.layout.component.Component
122437  * @private
122438  */
122439
122440 Ext.define('Ext.layout.component.Body', {
122441
122442     /* Begin Definitions */
122443
122444     alias: ['layout.body'],
122445
122446     extend: 'Ext.layout.component.Component',
122447
122448     uses: ['Ext.layout.container.Container'],
122449
122450     /* End Definitions */
122451
122452     type: 'body',
122453     
122454     onLayout: function(width, height) {
122455         var me = this,
122456             owner = me.owner;
122457
122458         // Size the Component's encapsulating element according to the dimensions
122459         me.setTargetSize(width, height);
122460
122461         // Size the Component's body element according to the content box of the encapsulating element
122462         me.setBodySize.apply(me, arguments);
122463
122464         // We need to bind to the owner whenever we do not have a user set height or width.
122465         if (owner && owner.layout && owner.layout.isLayout) {
122466             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
122467                 owner.layout.bindToOwnerCtComponent = true;
122468             }
122469             else {
122470                 owner.layout.bindToOwnerCtComponent = false;
122471             }
122472         }
122473         
122474         me.callParent(arguments);
122475     },
122476
122477     /**
122478      * @private
122479      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
122480      */
122481     setBodySize: function(width, height) {
122482         var me = this,
122483             owner = me.owner,
122484             frameSize = owner.frameSize,
122485             isNumber = Ext.isNumber;
122486
122487         if (isNumber(width)) {
122488             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
122489         }
122490         if (isNumber(height)) {
122491             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
122492         }
122493
122494         me.setElementSize(owner.body, width, height);
122495     }
122496 });
122497 /**
122498  * Component layout for Ext.form.FieldSet components
122499  * @class Ext.layout.component.FieldSet
122500  * @extends Ext.layout.component.Body
122501  * @private
122502  */
122503 Ext.define('Ext.layout.component.FieldSet', {
122504     extend: 'Ext.layout.component.Body',
122505     alias: ['layout.fieldset'],
122506
122507     type: 'fieldset',
122508
122509     doContainerLayout: function() {
122510         // Prevent layout/rendering of children if the fieldset is collapsed
122511         if (!this.owner.collapsed) {
122512             this.callParent();
122513         }
122514     }
122515 });
122516 /**
122517  * Component layout for tabs
122518  * @class Ext.layout.component.Tab
122519  * @extends Ext.layout.component.Button
122520  * @private
122521  */
122522 Ext.define('Ext.layout.component.Tab', {
122523
122524     alias: ['layout.tab'],
122525
122526     extend: 'Ext.layout.component.Button',
122527
122528     //type: 'button',
122529
122530     beforeLayout: function() {
122531         var me = this, dirty = me.lastClosable !== me.owner.closable;
122532
122533         if (dirty) {
122534             delete me.adjWidth;
122535         }
122536
122537         return this.callParent(arguments) || dirty;
122538     },
122539
122540     onLayout: function () {
122541         var me = this;
122542
122543         me.callParent(arguments);
122544
122545         me.lastClosable = me.owner.closable;
122546     }
122547 });
122548 /**
122549  * @private
122550  * @class Ext.layout.component.field.File
122551  * @extends Ext.layout.component.field.Field
122552  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
122553  * the file picker trigger button.
122554  * @private
122555  */
122556
122557 Ext.define('Ext.layout.component.field.File', {
122558     alias: ['layout.filefield'],
122559     extend: 'Ext.layout.component.field.Field',
122560
122561     type: 'filefield',
122562
122563     sizeBodyContents: function(width, height) {
122564         var me = this,
122565             owner = me.owner;
122566
122567         if (!owner.buttonOnly) {
122568             // Decrease the field's width by the width of the button and the configured buttonMargin.
122569             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
122570             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
122571         }
122572     }
122573 });
122574 /**
122575  * @class Ext.layout.component.field.Slider
122576  * @extends Ext.layout.component.field.Field
122577  * @private
122578  */
122579
122580 Ext.define('Ext.layout.component.field.Slider', {
122581
122582     /* Begin Definitions */
122583
122584     alias: ['layout.sliderfield'],
122585
122586     extend: 'Ext.layout.component.field.Field',
122587
122588     /* End Definitions */
122589
122590     type: 'sliderfield',
122591
122592     sizeBodyContents: function(width, height) {
122593         var owner = this.owner,
122594             thumbs = owner.thumbs,
122595             length = thumbs.length,
122596             inputEl = owner.inputEl,
122597             innerEl = owner.innerEl,
122598             endEl = owner.endEl,
122599             i = 0;
122600
122601         /*
122602          * If we happen to be animating during a resize, the position of the thumb will likely be off
122603          * when the animation stops. As such, just stop any animations before syncing the thumbs.
122604          */
122605         for(; i < length; ++i) {
122606             thumbs[i].el.stopAnimation();
122607         }
122608         
122609         if (owner.vertical) {
122610             inputEl.setHeight(height);
122611             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
122612         }
122613         else {
122614             inputEl.setWidth(width);
122615             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
122616         }
122617         owner.syncThumbs();
122618     }
122619 });
122620
122621 /**
122622  * @class Ext.layout.container.Absolute
122623  * @extends Ext.layout.container.Anchor
122624  *
122625  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
122626  * ability for x/y positioning using the standard x and y component config options.
122627  *
122628  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
122629  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
122630  *
122631  *     @example
122632  *     Ext.create('Ext.form.Panel', {
122633  *         title: 'Absolute Layout',
122634  *         width: 300,
122635  *         height: 275,
122636  *         layout:'absolute',
122637  *         layoutConfig: {
122638  *             // layout-specific configs go here
122639  *             //itemCls: 'x-abs-layout-item',
122640  *         },
122641  *         url:'save-form.php',
122642  *         defaultType: 'textfield',
122643  *         items: [{
122644  *             x: 10,
122645  *             y: 10,
122646  *             xtype:'label',
122647  *             text: 'Send To:'
122648  *         },{
122649  *             x: 80,
122650  *             y: 10,
122651  *             name: 'to',
122652  *             anchor:'90%'  // anchor width by percentage
122653  *         },{
122654  *             x: 10,
122655  *             y: 40,
122656  *             xtype:'label',
122657  *             text: 'Subject:'
122658  *         },{
122659  *             x: 80,
122660  *             y: 40,
122661  *             name: 'subject',
122662  *             anchor: '90%'  // anchor width by percentage
122663  *         },{
122664  *             x:0,
122665  *             y: 80,
122666  *             xtype: 'textareafield',
122667  *             name: 'msg',
122668  *             anchor: '100% 100%'  // anchor width and height
122669  *         }],
122670  *         renderTo: Ext.getBody()
122671  *     });
122672  */
122673 Ext.define('Ext.layout.container.Absolute', {
122674
122675     /* Begin Definitions */
122676
122677     alias: 'layout.absolute',
122678     extend: 'Ext.layout.container.Anchor',
122679     alternateClassName: 'Ext.layout.AbsoluteLayout',
122680
122681     /* End Definitions */
122682
122683     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
122684
122685     type: 'absolute',
122686
122687     onLayout: function() {
122688         var me = this,
122689             target = me.getTarget(),
122690             targetIsBody = target.dom === document.body;
122691
122692         // Do not set position: relative; when the absolute layout target is the body
122693         if (!targetIsBody) {
122694             target.position();
122695         }
122696         me.paddingLeft = target.getPadding('l');
122697         me.paddingTop = target.getPadding('t');
122698         me.callParent(arguments);
122699     },
122700
122701     // private
122702     adjustWidthAnchor: function(value, comp) {
122703         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
122704         return value ? value - comp.getPosition(true)[0] : value;
122705     },
122706
122707     // private
122708     adjustHeightAnchor: function(value, comp) {
122709         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
122710         return value ? value - comp.getPosition(true)[1] : value;
122711     }
122712 });
122713 /**
122714  * @class Ext.layout.container.Accordion
122715  * @extends Ext.layout.container.VBox
122716  *
122717  * This is a layout that manages multiple Panels in an expandable accordion style such that only
122718  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
122719  *
122720  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
122721  *
122722  *     @example
122723  *     Ext.create('Ext.panel.Panel', {
122724  *         title: 'Accordion Layout',
122725  *         width: 300,
122726  *         height: 300,
122727  *         layout:'accordion',
122728  *         defaults: {
122729  *             // applied to each contained panel
122730  *             bodyStyle: 'padding:15px'
122731  *         },
122732  *         layoutConfig: {
122733  *             // layout-specific configs go here
122734  *             titleCollapse: false,
122735  *             animate: true,
122736  *             activeOnTop: true
122737  *         },
122738  *         items: [{
122739  *             title: 'Panel 1',
122740  *             html: 'Panel content!'
122741  *         },{
122742  *             title: 'Panel 2',
122743  *             html: 'Panel content!'
122744  *         },{
122745  *             title: 'Panel 3',
122746  *             html: 'Panel content!'
122747  *         }],
122748  *         renderTo: Ext.getBody()
122749  *     });
122750  */
122751 Ext.define('Ext.layout.container.Accordion', {
122752     extend: 'Ext.layout.container.VBox',
122753     alias: ['layout.accordion'],
122754     alternateClassName: 'Ext.layout.AccordionLayout',
122755
122756     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
122757
122758     align: 'stretch',
122759
122760     /**
122761      * @cfg {Boolean} fill
122762      * True to adjust the active item's height to fill the available space in the container, false to use the
122763      * item's current height, or auto height if not explicitly set.
122764      */
122765     fill : true,
122766
122767     /**
122768      * @cfg {Boolean} autoWidth
122769      * Child Panels have their width actively managed to fit within the accordion's width.
122770      * @deprecated This config is ignored in ExtJS 4
122771      */
122772     autoWidth : true,
122773
122774     /**
122775      * @cfg {Boolean} titleCollapse
122776      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
122777      * expand/collapse only when the toggle tool button is clicked.  When set to false,
122778      * {@link #hideCollapseTool} should be false also.
122779      */
122780     titleCollapse : true,
122781
122782     /**
122783      * @cfg {Boolean} hideCollapseTool
122784      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
122785      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
122786      */
122787     hideCollapseTool : false,
122788
122789     /**
122790      * @cfg {Boolean} collapseFirst
122791      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
122792      * in the contained Panels' title bars, false to render it last.
122793      */
122794     collapseFirst : false,
122795
122796     /**
122797      * @cfg {Boolean} animate
122798      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
122799      * close directly with no animation. Note: The layout performs animated collapsing
122800      * and expanding, <i>not</i> the child Panels.
122801      */
122802     animate : true,
122803     /**
122804      * @cfg {Boolean} activeOnTop
122805      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
122806      *
122807      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
122808      * false to keep the panels in the rendered order.
122809      */
122810     activeOnTop : false,
122811     /**
122812      * @cfg {Boolean} multi
122813      * Set to <code>true</code> to enable multiple accordion items to be open at once.
122814      */
122815     multi: false,
122816
122817     constructor: function() {
122818         var me = this;
122819
122820         me.callParent(arguments);
122821
122822         // animate flag must be false during initial render phase so we don't get animations.
122823         me.initialAnimate = me.animate;
122824         me.animate = false;
122825
122826         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
122827         if (me.fill === false) {
122828             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
122829         }
122830     },
122831
122832     // Cannot lay out a fitting accordion before we have been allocated a height.
122833     // So during render phase, layout will not be performed.
122834     beforeLayout: function() {
122835         var me = this;
122836
122837         me.callParent(arguments);
122838         if (me.fill) {
122839             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
122840                 return false;
122841             }
122842         } else {
122843             me.owner.componentLayout.monitorChildren = false;
122844             me.autoSize = true;
122845             me.owner.setAutoScroll(true);
122846         }
122847     },
122848
122849     renderItems : function(items, target) {
122850         var me = this,
122851             ln = items.length,
122852             i = 0,
122853             comp,
122854             targetSize = me.getLayoutTargetSize(),
122855             renderedPanels = [];
122856
122857         for (; i < ln; i++) {
122858             comp = items[i];
122859             if (!comp.rendered) {
122860                 renderedPanels.push(comp);
122861
122862                 // Set up initial properties for Panels in an accordion.
122863                 if (me.collapseFirst) {
122864                     comp.collapseFirst = me.collapseFirst;
122865                 }
122866                 if (me.hideCollapseTool) {
122867                     comp.hideCollapseTool = me.hideCollapseTool;
122868                     comp.titleCollapse = true;
122869                 }
122870                 else if (me.titleCollapse) {
122871                     comp.titleCollapse = me.titleCollapse;
122872                 }
122873
122874                 delete comp.hideHeader;
122875                 comp.collapsible = true;
122876                 comp.title = comp.title || '&#160;';
122877
122878                 // Set initial sizes
122879                 comp.width = targetSize.width;
122880                 if (me.fill) {
122881                     delete comp.height;
122882                     delete comp.flex;
122883
122884                     // If there is an expanded item, all others must be rendered collapsed.
122885                     if (me.expandedItem !== undefined) {
122886                         comp.collapsed = true;
122887                     }
122888                     // Otherwise expand the first item with collapsed explicitly configured as false
122889                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
122890                         comp.flex = 1;
122891                         me.expandedItem = i;
122892                     } else {
122893                         comp.collapsed = true;
122894                     }
122895                     // If we are fitting, then intercept expand/collapse requests.
122896                     me.owner.mon(comp, {
122897                         show: me.onComponentShow,
122898                         beforeexpand: me.onComponentExpand,
122899                         beforecollapse: me.onComponentCollapse,
122900                         scope: me
122901                     });
122902                 } else {
122903                     delete comp.flex;
122904                     comp.animCollapse = me.initialAnimate;
122905                     comp.autoHeight = true;
122906                     comp.autoScroll = false;
122907                 }
122908                 comp.border = comp.collapsed;
122909             }
122910         }
122911
122912         // If no collapsed:false Panels found, make the first one expanded.
122913         if (ln && me.expandedItem === undefined) {
122914             me.expandedItem = 0;
122915             comp = items[0];
122916             comp.collapsed = comp.border = false;
122917             if (me.fill) {
122918                 comp.flex = 1;
122919             }
122920         }
122921
122922         // Render all Panels.
122923         me.callParent(arguments);
122924
122925         // Postprocess rendered Panels.
122926         ln = renderedPanels.length;
122927         for (i = 0; i < ln; i++) {
122928             comp = renderedPanels[i];
122929
122930             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
122931             delete comp.width;
122932
122933             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
122934             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
122935         }
122936     },
122937
122938     onLayout: function() {
122939         var me = this;
122940
122941
122942         if (me.fill) {
122943             me.callParent(arguments);
122944         } else {
122945             var targetSize = me.getLayoutTargetSize(),
122946                 items = me.getVisibleItems(),
122947                 len = items.length,
122948                 i = 0, comp;
122949
122950             for (; i < len; i++) {
122951                 comp = items[i];
122952                 if (comp.collapsed) {
122953                     items[i].setWidth(targetSize.width);
122954                 } else {
122955                     items[i].setSize(null, null);
122956                 }
122957             }
122958         }
122959         me.updatePanelClasses();
122960
122961         return me;
122962     },
122963
122964     updatePanelClasses: function() {
122965         var children = this.getLayoutItems(),
122966             ln = children.length,
122967             siblingCollapsed = true,
122968             i, child;
122969
122970         for (i = 0; i < ln; i++) {
122971             child = children[i];
122972
122973             // Fix for EXTJSIV-3724. Windows only.
122974             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
122975             if (Ext.isWindows) {
122976                 child.el.dom.scrollTop = 0;
122977             }
122978
122979             if (siblingCollapsed) {
122980                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
122981             }
122982             else {
122983                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
122984             }
122985
122986             if (i + 1 == ln && child.collapsed) {
122987                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
122988             }
122989             else {
122990                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
122991             }
122992             siblingCollapsed = child.collapsed;
122993         }
122994     },
122995     
122996     animCallback: function(){
122997         Ext.Array.forEach(this.toCollapse, function(comp){
122998             comp.fireEvent('collapse', comp);
122999         });
123000         
123001         Ext.Array.forEach(this.toExpand, function(comp){
123002             comp.fireEvent('expand', comp);
123003         });    
123004     },
123005     
123006     setupEvents: function(){
123007         this.toCollapse = [];
123008         this.toExpand = [];    
123009     },
123010
123011     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
123012     // their headers.
123013     // The expanded Component receives the only flex value, and so gets all remaining space.
123014     onComponentExpand: function(toExpand) {
123015         var me = this,
123016             it = me.owner.items.items,
123017             len = it.length,
123018             i = 0,
123019             comp;
123020
123021         me.setupEvents();
123022         for (; i < len; i++) {
123023             comp = it[i];
123024             if (comp === toExpand && comp.collapsed) {
123025                 me.setExpanded(comp);
123026             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
123027                 me.setCollapsed(comp);
123028             }
123029         }
123030
123031         me.animate = me.initialAnimate;
123032         if (me.activeOnTop) {
123033             // insert will trigger a layout
123034             me.owner.insert(0, toExpand); 
123035         } else {
123036             me.layout();
123037         }
123038         me.animate = false;
123039         return false;
123040     },
123041
123042     onComponentCollapse: function(comp) {
123043         var me = this,
123044             toExpand = comp.next() || comp.prev(),
123045             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
123046
123047         me.setupEvents();
123048         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
123049         // then ask the box layout to collapse it to its header.
123050         if (me.multi) {
123051             me.setCollapsed(comp);
123052
123053             // If the collapsing Panel is the only expanded one, expand the following Component.
123054             // All this is handling fill: true, so there must be at least one expanded,
123055             if (expanded.length === 1 && expanded[0] === comp) {
123056                 me.setExpanded(toExpand);
123057             }
123058
123059             me.animate = me.initialAnimate;
123060             me.layout();
123061             me.animate = false;
123062         }
123063         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
123064         else if (toExpand) {
123065             me.onComponentExpand(toExpand);
123066         }
123067         return false;
123068     },
123069
123070     onComponentShow: function(comp) {
123071         // Showing a Component means that you want to see it, so expand it.
123072         this.onComponentExpand(comp);
123073     },
123074
123075     setCollapsed: function(comp) {
123076         var otherDocks = comp.getDockedItems(),
123077             dockItem,
123078             len = otherDocks.length,
123079             i = 0;
123080
123081         // Hide all docked items except the header
123082         comp.hiddenDocked = [];
123083         for (; i < len; i++) {
123084             dockItem = otherDocks[i];
123085             if ((dockItem !== comp.header) && !dockItem.hidden) {
123086                 dockItem.hidden = true;
123087                 comp.hiddenDocked.push(dockItem);
123088             }
123089         }
123090         comp.addCls(comp.collapsedCls);
123091         comp.header.addCls(comp.collapsedHeaderCls);
123092         comp.height = comp.header.getHeight();
123093         comp.el.setHeight(comp.height);
123094         comp.collapsed = true;
123095         delete comp.flex;
123096         if (this.initialAnimate) {
123097             this.toCollapse.push(comp);
123098         } else {
123099             comp.fireEvent('collapse', comp);
123100         }
123101         if (comp.collapseTool) {
123102             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
123103         }
123104     },
123105
123106     setExpanded: function(comp) {
123107         var otherDocks = comp.hiddenDocked,
123108             len = otherDocks ? otherDocks.length : 0,
123109             i = 0;
123110
123111         // Show temporarily hidden docked items
123112         for (; i < len; i++) {
123113             otherDocks[i].show();
123114         }
123115
123116         // If it was an initial native collapse which hides the body
123117         if (!comp.body.isVisible()) {
123118             comp.body.show();
123119         }
123120         delete comp.collapsed;
123121         delete comp.height;
123122         delete comp.componentLayout.lastComponentSize;
123123         comp.suspendLayout = false;
123124         comp.flex = 1;
123125         comp.removeCls(comp.collapsedCls);
123126         comp.header.removeCls(comp.collapsedHeaderCls);
123127          if (this.initialAnimate) {
123128             this.toExpand.push(comp);
123129         } else {
123130             comp.fireEvent('expand', comp);
123131         }
123132         if (comp.collapseTool) {
123133             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
123134         }
123135         comp.setAutoScroll(comp.initialConfig.autoScroll);
123136     }
123137 });
123138 /**
123139  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
123140  * layout to resize both immediate siblings.
123141  *
123142  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
123143  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
123144  * by the layout.
123145  *
123146  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
123147  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
123148  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
123149  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
123150  */
123151 Ext.define('Ext.resizer.Splitter', {
123152     extend: 'Ext.Component',
123153     requires: ['Ext.XTemplate'],
123154     uses: ['Ext.resizer.SplitterTracker'],
123155     alias: 'widget.splitter',
123156
123157     renderTpl: [
123158         '<tpl if="collapsible===true">',
123159             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
123160                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
123161         '</tpl>'
123162     ],
123163
123164     baseCls: Ext.baseCSSPrefix + 'splitter',
123165     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
123166
123167     /**
123168      * @cfg {Boolean} collapsible
123169      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
123170      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
123171      */
123172     collapsible: false,
123173
123174     /**
123175      * @cfg {Boolean} performCollapse
123176      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
123177      * state of the {@link #collapseTarget}.</p>
123178      */
123179
123180     /**
123181      * @cfg {Boolean} collapseOnDblClick
123182      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
123183      */
123184     collapseOnDblClick: true,
123185
123186     /**
123187      * @cfg {Number} defaultSplitMin
123188      * Provides a default minimum width or height for the two components
123189      * that the splitter is between.
123190      */
123191     defaultSplitMin: 40,
123192
123193     /**
123194      * @cfg {Number} defaultSplitMax
123195      * Provides a default maximum width or height for the two components
123196      * that the splitter is between.
123197      */
123198     defaultSplitMax: 1000,
123199
123200     /**
123201      * @cfg {String} collapsedCls
123202      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
123203      */
123204
123205     width: 5,
123206     height: 5,
123207
123208     /**
123209      * @cfg {String/Ext.panel.Panel} collapseTarget
123210      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
123211      * <p>Or the immediate sibling Panel to collapse.</p>
123212      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
123213      * <p><b>Note that only Panels may be collapsed.</b></p>
123214      */
123215     collapseTarget: 'next',
123216
123217     /**
123218      * @property orientation
123219      * @type String
123220      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
123221      * when used in a vbox layout.
123222      */
123223
123224     onRender: function() {
123225         var me = this,
123226             target = me.getCollapseTarget(),
123227             collapseDir = me.getCollapseDirection();
123228
123229         Ext.applyIf(me.renderData, {
123230             collapseDir: collapseDir,
123231             collapsible: me.collapsible || target.collapsible
123232         });
123233
123234         me.addChildEls('collapseEl');
123235
123236         this.callParent(arguments);
123237
123238         // Add listeners on the mini-collapse tool unless performCollapse is set to false
123239         if (me.performCollapse !== false) {
123240             if (me.renderData.collapsible) {
123241                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
123242             }
123243             if (me.collapseOnDblClick) {
123244                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
123245             }
123246         }
123247
123248         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
123249         me.mon(target, 'collapse', me.onTargetCollapse, me);
123250         me.mon(target, 'expand', me.onTargetExpand, me);
123251
123252         me.el.addCls(me.baseCls + '-' + me.orientation);
123253         me.el.unselectable();
123254
123255         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
123256             el: me.el
123257         });
123258
123259         // Relay the most important events to our owner (could open wider later):
123260         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
123261     },
123262
123263     getCollapseDirection: function() {
123264         var me = this,
123265             idx,
123266             type = me.ownerCt.layout.type;
123267
123268         // Avoid duplication of string tests.
123269         // Create a two bit truth table of the configuration of the Splitter:
123270         // Collapse Target | orientation
123271         //        0              0             = next, horizontal
123272         //        0              1             = next, vertical
123273         //        1              0             = prev, horizontal
123274         //        1              1             = prev, vertical
123275         if (me.collapseTarget.isComponent) {
123276             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
123277         } else {
123278             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
123279         }
123280
123281         // Read the data out the truth table
123282         me.orientation = ['horizontal', 'vertical'][idx & 1];
123283         return ['bottom', 'right', 'top', 'left'][idx];
123284     },
123285
123286     getCollapseTarget: function() {
123287         var me = this;
123288
123289         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
123290     },
123291
123292     onTargetCollapse: function(target) {
123293         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
123294     },
123295
123296     onTargetExpand: function(target) {
123297         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
123298     },
123299
123300     toggleTargetCmp: function(e, t) {
123301         var cmp = this.getCollapseTarget();
123302
123303         if (cmp.isVisible()) {
123304             // restore
123305             if (cmp.collapsed) {
123306                 cmp.expand(cmp.animCollapse);
123307             // collapse
123308             } else {
123309                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
123310             }
123311         }
123312     },
123313
123314     /*
123315      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
123316      */
123317     setSize: function() {
123318         var me = this;
123319         me.callParent(arguments);
123320         if (Ext.isIE) {
123321             me.el.repaint();
123322         }
123323     }
123324 });
123325
123326 /**
123327  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
123328  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
123329  *
123330  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
123331  * config, and should generally not need to be created directly via the new keyword.
123332  *
123333  *     @example
123334  *     Ext.create('Ext.panel.Panel', {
123335  *         width: 500,
123336  *         height: 400,
123337  *         title: 'Border Layout',
123338  *         layout: 'border',
123339  *         items: [{
123340  *             title: 'South Region is resizable',
123341  *             region: 'south',     // position for region
123342  *             xtype: 'panel',
123343  *             height: 100,
123344  *             split: true,         // enable resizing
123345  *             margins: '0 5 5 5'
123346  *         },{
123347  *             // xtype: 'panel' implied by default
123348  *             title: 'West Region is collapsible',
123349  *             region:'west',
123350  *             xtype: 'panel',
123351  *             margins: '5 0 0 5',
123352  *             width: 200,
123353  *             collapsible: true,   // make collapsible
123354  *             id: 'west-region-container',
123355  *             layout: 'fit'
123356  *         },{
123357  *             title: 'Center Region',
123358  *             region: 'center',     // center region is required, no width/height specified
123359  *             xtype: 'panel',
123360  *             layout: 'fit',
123361  *             margins: '5 5 0 0'
123362  *         }],
123363  *         renderTo: Ext.getBody()
123364  *     });
123365  *
123366  * # Notes
123367  *
123368  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
123369  *   The child item in the center region will always be resized to fill the remaining space
123370  *   not used by the other regions in the layout.
123371  *
123372  * - Any child items with a region of `west` or `east` may be configured with either an initial
123373  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
123374  *   **string** (Which is simply divided by 100 and used as a flex value).
123375  *   The 'center' region has a flex value of `1`.
123376  *
123377  * - Any child items with a region of `north` or `south` may be configured with either an initial
123378  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
123379  *   **string** (Which is simply divided by 100 and used as a flex value).
123380  *   The 'center' region has a flex value of `1`.
123381  *
123382  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
123383  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
123384  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
123385  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
123386  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
123387  *   the west region:
123388  *
123389  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
123390  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
123391  *       wrc.{@link Ext.container.Container#add add}({
123392  *           title: 'Added Panel',
123393  *           html: 'Some content'
123394  *       });
123395  *
123396  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
123397  */
123398 Ext.define('Ext.layout.container.Border', {
123399
123400     alias: ['layout.border'],
123401     extend: 'Ext.layout.container.Container',
123402     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
123403     alternateClassName: 'Ext.layout.BorderLayout',
123404
123405     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
123406
123407     itemCls: Ext.baseCSSPrefix + 'border-item',
123408
123409     bindToOwnerCtContainer: true,
123410
123411     percentageRe: /(\d+)%/,
123412
123413     slideDirection: {
123414         north: 't',
123415         south: 'b',
123416         west: 'l',
123417         east: 'r'
123418     },
123419
123420     constructor: function(config) {
123421         this.initialConfig = config;
123422         this.callParent(arguments);
123423     },
123424
123425     onLayout: function() {
123426         var me = this;
123427         if (!me.borderLayoutInitialized) {
123428             me.initializeBorderLayout();
123429         }
123430
123431         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
123432         me.fixHeightConstraints();
123433         me.shadowLayout.onLayout();
123434         if (me.embeddedContainer) {
123435             me.embeddedContainer.layout.onLayout();
123436         }
123437
123438         // If the panel was originally configured with collapsed: true, it will have
123439         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
123440         if (!me.initialCollapsedComplete) {
123441             Ext.iterate(me.regions, function(name, region){
123442                 if (region.borderCollapse) {
123443                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
123444                 }
123445             });
123446             me.initialCollapsedComplete = true;
123447         }
123448     },
123449
123450     isValidParent : function(item, target, position) {
123451         if (!this.borderLayoutInitialized) {
123452             this.initializeBorderLayout();
123453         }
123454
123455         // Delegate this operation to the shadow "V" or "H" box layout.
123456         return this.shadowLayout.isValidParent(item, target, position);
123457     },
123458
123459     beforeLayout: function() {
123460         if (!this.borderLayoutInitialized) {
123461             this.initializeBorderLayout();
123462         }
123463
123464         // Delegate this operation to the shadow "V" or "H" box layout.
123465         this.shadowLayout.beforeLayout();
123466
123467         // note: don't call base because that does a renderItems again
123468     },
123469
123470     renderItems: function(items, target) {
123471     },
123472
123473     renderItem: function(item) {
123474     },
123475
123476     renderChildren: function() {
123477         if (!this.borderLayoutInitialized) {
123478             this.initializeBorderLayout();
123479         }
123480
123481         this.shadowLayout.renderChildren();
123482     },
123483
123484     /*
123485      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
123486      * We must not include child items which are floated over the layout (are primed with a slide out animation)
123487      */
123488     getVisibleItems: function() {
123489         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
123490     },
123491
123492     initializeBorderLayout: function() {
123493         var me = this,
123494             i = 0,
123495             items = me.getLayoutItems(),
123496             ln = items.length,
123497             regions = (me.regions = {}),
123498             vBoxItems = [],
123499             hBoxItems = [],
123500             horizontalFlex = 0,
123501             verticalFlex = 0,
123502             comp, percentage;
123503
123504         // Map of Splitters for each region
123505         me.splitters = {};
123506
123507         // Map of regions
123508         for (; i < ln; i++) {
123509             comp = items[i];
123510             regions[comp.region] = comp;
123511
123512             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
123513             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
123514
123515                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
123516                 comp.borderCollapse = comp.collapsed;
123517                 comp.collapsed = false;
123518
123519                 comp.on({
123520                     beforecollapse: me.onBeforeRegionCollapse,
123521                     beforeexpand: me.onBeforeRegionExpand,
123522                     destroy: me.onRegionDestroy,
123523                     scope: me
123524                 });
123525                 me.setupState(comp);
123526             }
123527         }
123528         comp = regions.center;
123529         if (!comp.flex) {
123530             comp.flex = 1;
123531         }
123532         delete comp.width;
123533         comp.maintainFlex = true;
123534
123535         // Begin the VBox and HBox item list.
123536         comp = regions.west;
123537         if (comp) {
123538             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
123539             hBoxItems.push(comp);
123540             if (comp.split) {
123541                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
123542             }
123543             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123544             if (percentage) {
123545                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123546                 delete comp.width;
123547             }
123548         }
123549         comp = regions.north;
123550         if (comp) {
123551             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
123552             vBoxItems.push(comp);
123553             if (comp.split) {
123554                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
123555             }
123556             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123557             if (percentage) {
123558                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123559                 delete comp.height;
123560             }
123561         }
123562
123563         // Decide into which Collection the center region goes.
123564         if (regions.north || regions.south) {
123565             if (regions.east || regions.west) {
123566
123567                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
123568                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
123569                     xtype: 'container',
123570                     region: 'center',
123571                     id: me.owner.id + '-embedded-center',
123572                     cls: Ext.baseCSSPrefix + 'border-item',
123573                     flex: regions.center.flex,
123574                     maintainFlex: true,
123575                     layout: {
123576                         type: 'hbox',
123577                         align: 'stretch',
123578                         getVisibleItems: me.getVisibleItems
123579                     }
123580                 }));
123581                 hBoxItems.push(regions.center);
123582             }
123583             // No east or west: the original center goes straight into the vbox
123584             else {
123585                 vBoxItems.push(regions.center);
123586             }
123587         }
123588         // If we have no north or south, then the center is part of the HBox items
123589         else {
123590             hBoxItems.push(regions.center);
123591         }
123592
123593         // Finish off the VBox and HBox item list.
123594         comp = regions.south;
123595         if (comp) {
123596             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
123597             if (comp.split) {
123598                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
123599             }
123600             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123601             if (percentage) {
123602                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123603                 delete comp.height;
123604             }
123605             vBoxItems.push(comp);
123606         }
123607         comp = regions.east;
123608         if (comp) {
123609             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
123610             if (comp.split) {
123611                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
123612             }
123613             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123614             if (percentage) {
123615                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123616                 delete comp.width;
123617             }
123618             hBoxItems.push(comp);
123619         }
123620
123621         // Create the injected "items" collections for the Containers.
123622         // If we have north or south, then the shadow Container will be a VBox.
123623         // If there are also east or west regions, its center will be a shadow HBox.
123624         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
123625         if (regions.north || regions.south) {
123626
123627             me.shadowContainer = Ext.create('Ext.container.Container', {
123628                 ownerCt: me.owner,
123629                 el: me.getTarget(),
123630                 layout: Ext.applyIf({
123631                     type: 'vbox',
123632                     align: 'stretch',
123633                     getVisibleItems: me.getVisibleItems
123634                 }, me.initialConfig)
123635             });
123636             me.createItems(me.shadowContainer, vBoxItems);
123637
123638             // Allow the Splitters to orientate themselves
123639             if (me.splitters.north) {
123640                 me.splitters.north.ownerCt = me.shadowContainer;
123641             }
123642             if (me.splitters.south) {
123643                 me.splitters.south.ownerCt = me.shadowContainer;
123644             }
123645
123646             // Inject items into the HBox Container if there is one - if there was an east or west.
123647             if (me.embeddedContainer) {
123648                 me.embeddedContainer.ownerCt = me.shadowContainer;
123649                 me.createItems(me.embeddedContainer, hBoxItems);
123650
123651                 // Allow the Splitters to orientate themselves
123652                 if (me.splitters.east) {
123653                     me.splitters.east.ownerCt = me.embeddedContainer;
123654                 }
123655                 if (me.splitters.west) {
123656                     me.splitters.west.ownerCt = me.embeddedContainer;
123657                 }
123658
123659                 // These spliiters need to be constrained by components one-level below
123660                 // the component in their vobx. We update the min/maxHeight on the helper
123661                 // (embeddedContainer) prior to starting the split/drag. This has to be
123662                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
123663                 // dynamically.
123664                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
123665                     if (splitter) {
123666                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
123667                     }
123668                 });
123669
123670                 // The east or west region wanted a percentage
123671                 if (horizontalFlex) {
123672                     regions.center.flex -= horizontalFlex;
123673                 }
123674                 // The north or south region wanted a percentage
123675                 if (verticalFlex) {
123676                     me.embeddedContainer.flex -= verticalFlex;
123677                 }
123678             } else {
123679                 // The north or south region wanted a percentage
123680                 if (verticalFlex) {
123681                     regions.center.flex -= verticalFlex;
123682                 }
123683             }
123684         }
123685         // If we have no north or south, then there's only one Container, and it's
123686         // an HBox, or, if only a center region was specified, a Fit.
123687         else {
123688             me.shadowContainer = Ext.create('Ext.container.Container', {
123689                 ownerCt: me.owner,
123690                 el: me.getTarget(),
123691                 layout: Ext.applyIf({
123692                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
123693                     align: 'stretch'
123694                 }, me.initialConfig)
123695             });
123696             me.createItems(me.shadowContainer, hBoxItems);
123697
123698             // Allow the Splitters to orientate themselves
123699             if (me.splitters.east) {
123700                 me.splitters.east.ownerCt = me.shadowContainer;
123701             }
123702             if (me.splitters.west) {
123703                 me.splitters.west.ownerCt = me.shadowContainer;
123704             }
123705
123706             // The east or west region wanted a percentage
123707             if (horizontalFlex) {
123708                 regions.center.flex -= verticalFlex;
123709             }
123710         }
123711
123712         // Create upward links from the region Components to their shadow ownerCts
123713         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
123714             items[i].shadowOwnerCt = me.shadowContainer;
123715         }
123716         if (me.embeddedContainer) {
123717             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
123718                 items[i].shadowOwnerCt = me.embeddedContainer;
123719             }
123720         }
123721
123722         // This is the layout that we delegate all operations to
123723         me.shadowLayout = me.shadowContainer.getLayout();
123724
123725         me.borderLayoutInitialized = true;
123726     },
123727
123728     setupState: function(comp){
123729         var getState = comp.getState;
123730         comp.getState = function(){
123731             // call the original getState
123732             var state = getState.call(comp) || {},
123733                 region = comp.region;
123734
123735             state.collapsed = !!comp.collapsed;
123736             if (region == 'west' || region == 'east') {
123737                 state.width = comp.getWidth();
123738             } else {
123739                 state.height = comp.getHeight();
123740             }
123741             return state;
123742         };
123743         comp.addStateEvents(['collapse', 'expand', 'resize']);
123744     },
123745
123746     /**
123747      * Create the items collection for our shadow/embedded containers
123748      * @private
123749      */
123750     createItems: function(container, items){
123751         // Have to inject an items Collection *after* construction.
123752         // The child items of the shadow layout must retain their original, user-defined ownerCt
123753         delete container.items;
123754         container.initItems();
123755         container.items.addAll(items);
123756     },
123757
123758     // Private
123759     // Create a splitter for a child of the layout.
123760     createSplitter: function(comp) {
123761         var me = this,
123762             interceptCollapse = (comp.collapseMode != 'header'),
123763             resizer;
123764
123765         resizer = Ext.create('Ext.resizer.Splitter', {
123766             hidden: !!comp.hidden,
123767             collapseTarget: comp,
123768             performCollapse: !interceptCollapse,
123769             listeners: interceptCollapse ? {
123770                 click: {
123771                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123772                     element: 'collapseEl'
123773                 }
123774             } : null
123775         });
123776
123777         // Mini collapse means that the splitter is the placeholder Component
123778         if (comp.collapseMode == 'mini') {
123779             comp.placeholder = resizer;
123780             resizer.collapsedCls = comp.collapsedCls;
123781         }
123782
123783         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
123784         comp.on({
123785             hide: me.onRegionVisibilityChange,
123786             show: me.onRegionVisibilityChange,
123787             scope: me
123788         });
123789         return resizer;
123790     },
123791
123792     // Private
123793     // Propagates the min/maxHeight values from the inner hbox items to its container.
123794     fixHeightConstraints: function () {
123795         var me = this,
123796             ct = me.embeddedContainer,
123797             maxHeight = 1e99, minHeight = -1;
123798
123799         if (!ct) {
123800             return;
123801         }
123802
123803         ct.items.each(function (item) {
123804             if (Ext.isNumber(item.maxHeight)) {
123805                 maxHeight = Math.max(maxHeight, item.maxHeight);
123806             }
123807             if (Ext.isNumber(item.minHeight)) {
123808                 minHeight = Math.max(minHeight, item.minHeight);
123809             }
123810         });
123811
123812         ct.maxHeight = maxHeight;
123813         ct.minHeight = minHeight;
123814     },
123815
123816     // Hide/show a region's associated splitter when the region is hidden/shown
123817     onRegionVisibilityChange: function(comp){
123818         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
123819         this.layout();
123820     },
123821
123822     // Called when a splitter mini-collapse tool is clicked on.
123823     // The listener is only added if this layout is controlling collapsing,
123824     // not if the component's collapseMode is 'mini' or 'header'.
123825     onSplitterCollapseClick: function(comp) {
123826         if (comp.collapsed) {
123827             this.onPlaceHolderToolClick(null, null, null, {client: comp});
123828         } else {
123829             comp.collapse();
123830         }
123831     },
123832
123833     /**
123834      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
123835      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
123836      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
123837      *
123838      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
123839      * collapsed.**
123840      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
123841      * Ext.panel.Panel#placeholder placeholder}.
123842      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
123843      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
123844      */
123845     getPlaceholder: function(comp) {
123846         var me = this,
123847             placeholder = comp.placeholder,
123848             shadowContainer = comp.shadowOwnerCt,
123849             shadowLayout = shadowContainer.layout,
123850             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
123851             horiz = (comp.region == 'north' || comp.region == 'south');
123852
123853         // No placeholder if the collapse mode is not the Border layout default
123854         if (comp.collapseMode == 'header') {
123855             return;
123856         }
123857
123858         // Provide a replacement Container with an expand tool
123859         if (!placeholder) {
123860             if (comp.collapseMode == 'mini') {
123861                 placeholder = Ext.create('Ext.resizer.Splitter', {
123862                     id: 'collapse-placeholder-' + comp.id,
123863                     collapseTarget: comp,
123864                     performCollapse: false,
123865                     listeners: {
123866                         click: {
123867                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123868                             element: 'collapseEl'
123869                         }
123870                     }
123871                 });
123872                 placeholder.addCls(placeholder.collapsedCls);
123873             } else {
123874                 placeholder = {
123875                     id: 'collapse-placeholder-' + comp.id,
123876                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
123877                     xtype: 'header',
123878                     orientation: horiz ? 'horizontal' : 'vertical',
123879                     title: comp.title,
123880                     textCls: comp.headerTextCls,
123881                     iconCls: comp.iconCls,
123882                     baseCls: comp.baseCls + '-header',
123883                     ui: comp.ui,
123884                     indicateDrag: comp.draggable,
123885                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
123886                     listeners: comp.floatable ? {
123887                         click: {
123888                             fn: function(e) {
123889                                 me.floatCollapsedPanel(e, comp);
123890                             },
123891                             element: 'el'
123892                         }
123893                     } : null
123894                 };
123895                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
123896                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
123897                     placeholder.width = 25;
123898                 }
123899                 if (!comp.hideCollapseTool) {
123900                     placeholder[horiz ? 'tools' : 'items'] = [{
123901                         xtype: 'tool',
123902                         client: comp,
123903                         type: 'expand-' + oppositeDirection,
123904                         handler: me.onPlaceHolderToolClick,
123905                         scope: me
123906                     }];
123907                 }
123908             }
123909             placeholder = me.owner.createComponent(placeholder);
123910             if (comp.isXType('panel')) {
123911                 comp.on({
123912                     titlechange: me.onRegionTitleChange,
123913                     iconchange: me.onRegionIconChange,
123914                     scope: me
123915                 });
123916             }
123917         }
123918
123919         // The collapsed Component holds a reference to its placeholder and vice versa
123920         comp.placeholder = placeholder;
123921         placeholder.comp = comp;
123922
123923         return placeholder;
123924     },
123925
123926     /**
123927      * @private
123928      * Update the placeholder title when panel title has been set or changed.
123929      */
123930     onRegionTitleChange: function(comp, newTitle) {
123931         comp.placeholder.setTitle(newTitle);
123932     },
123933
123934     /**
123935      * @private
123936      * Update the placeholder iconCls when panel iconCls has been set or changed.
123937      */
123938     onRegionIconChange: function(comp, newIconCls) {
123939         comp.placeholder.setIconCls(newIconCls);
123940     },
123941
123942     /**
123943      * @private
123944      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
123945      * when configured with a flex, calls this method on its ownerCt's layout.
123946      * @param {Ext.Component} child The child Component to calculate the box for
123947      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
123948      */
123949     calculateChildBox: function(comp) {
123950         var me = this;
123951         if (me.shadowContainer.items.contains(comp)) {
123952             return me.shadowContainer.layout.calculateChildBox(comp);
123953         }
123954         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
123955             return me.embeddedContainer.layout.calculateChildBox(comp);
123956         }
123957     },
123958
123959     /**
123960      * @private
123961      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
123962      * with a placeholder Header orientated in the appropriate dimension.
123963      * @param comp The Panel being collapsed.
123964      * @param direction
123965      * @param animate
123966      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
123967      */
123968     onBeforeRegionCollapse: function(comp, direction, animate) {
123969         if (comp.collapsedChangingLayout) {
123970             return false;
123971         }
123972         comp.collapsedChangingLayout = true;
123973         var me = this,
123974             compEl = comp.el,
123975             width,
123976             miniCollapse = comp.collapseMode == 'mini',
123977             shadowContainer = comp.shadowOwnerCt,
123978             shadowLayout = shadowContainer.layout,
123979             placeholder = comp.placeholder,
123980             sl = me.owner.suspendLayout,
123981             scsl = shadowContainer.suspendLayout,
123982             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
123983
123984         // Do not trigger a layout during transition to collapsed Component
123985         me.owner.suspendLayout = true;
123986         shadowContainer.suspendLayout = true;
123987
123988         // Prevent upward notifications from downstream layouts
123989         shadowLayout.layoutBusy = true;
123990         if (shadowContainer.componentLayout) {
123991             shadowContainer.componentLayout.layoutBusy = true;
123992         }
123993         me.shadowContainer.layout.layoutBusy = true;
123994         me.layoutBusy = true;
123995         me.owner.componentLayout.layoutBusy = true;
123996
123997         // Provide a replacement Container with an expand tool
123998         if (!placeholder) {
123999             placeholder = me.getPlaceholder(comp);
124000         }
124001
124002         // placeholder already in place; show it.
124003         if (placeholder.shadowOwnerCt === shadowContainer) {
124004             placeholder.show();
124005         }
124006         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
124007         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
124008         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
124009         else {
124010             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
124011             placeholder.shadowOwnerCt = shadowContainer;
124012             placeholder.ownerCt = me.owner;
124013         }
124014
124015         // Flag the collapsing Component as hidden and show the placeholder.
124016         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
124017         // We hide or slideOut the Component's element
124018         comp.hidden = true;
124019
124020         if (!placeholder.rendered) {
124021             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
124022
124023             // The inserted placeholder does not have the proper size, so copy the width
124024             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
124025             // without recursive layouts. This is only an issue initially. After this time,
124026             // placeholder will have the correct width/height set by the layout (which has
124027             // already happened when we get here initially).
124028             if (comp.region == 'north' || comp.region == 'south') {
124029                 placeholder.setCalculatedSize(comp.getWidth());
124030             } else {
124031                 placeholder.setCalculatedSize(undefined, comp.getHeight());
124032             }
124033         }
124034
124035         // Jobs to be done after the collapse has been done
124036         function afterCollapse() {
124037             // Reinstate automatic laying out.
124038             me.owner.suspendLayout = sl;
124039             shadowContainer.suspendLayout = scsl;
124040             delete shadowLayout.layoutBusy;
124041             if (shadowContainer.componentLayout) {
124042                 delete shadowContainer.componentLayout.layoutBusy;
124043             }
124044             delete me.shadowContainer.layout.layoutBusy;
124045             delete me.layoutBusy;
124046             delete me.owner.componentLayout.layoutBusy;
124047             delete comp.collapsedChangingLayout;
124048
124049             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
124050             comp.collapsed = true;
124051             comp.fireEvent('collapse', comp);
124052         }
124053
124054         /*
124055          * Set everything to the new positions. Note that we
124056          * only want to animate the collapse if it wasn't configured
124057          * initially with collapsed: true
124058          */
124059         if (comp.animCollapse && me.initialCollapsedComplete) {
124060             shadowLayout.layout();
124061             compEl.dom.style.zIndex = 100;
124062
124063             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124064             if (!miniCollapse) {
124065                 placeholder.el.hide();
124066             }
124067             compEl.slideOut(me.slideDirection[comp.region], {
124068                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124069                 listeners: {
124070                     afteranimate: function() {
124071                         compEl.show().setLeftTop(-10000, -10000);
124072                         compEl.dom.style.zIndex = '';
124073
124074                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124075                        if (!miniCollapse) {
124076                             placeholder.el.slideIn(me.slideDirection[comp.region], {
124077                                 easing: 'linear',
124078                                 duration: 100
124079                             });
124080                         }
124081                         afterCollapse();
124082                     }
124083                 }
124084             });
124085         } else {
124086             compEl.setLeftTop(-10000, -10000);
124087             shadowLayout.layout();
124088             afterCollapse();
124089         }
124090
124091         return false;
124092     },
124093
124094     // Hijack the expand operation to remove the placeholder and slide the region back in.
124095     onBeforeRegionExpand: function(comp, animate) {
124096         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
124097         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
124098         return false;
124099     },
124100
124101     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
124102     onPlaceHolderToolClick: function(e, target, owner, tool) {
124103         var me = this,
124104             comp = tool.client,
124105
124106             // Hide the placeholder unless it was the Component's preexisting splitter
124107             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
124108             compEl = comp.el,
124109             toCompBox,
124110             placeholder = comp.placeholder,
124111             placeholderEl = placeholder.el,
124112             shadowContainer = comp.shadowOwnerCt,
124113             shadowLayout = shadowContainer.layout,
124114             curSize,
124115             sl = me.owner.suspendLayout,
124116             scsl = shadowContainer.suspendLayout,
124117             isFloating;
124118
124119         if (comp.collapsedChangingLayout) {
124120             return false;
124121         }
124122         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
124123             return false;
124124         }
124125         comp.collapsedChangingLayout = true;
124126         // If the slide in is still going, stop it.
124127         // This will either leave the Component in its fully floated state (which is processed below)
124128         // or in its collapsed state. Either way, we expand it..
124129         if (comp.getActiveAnimation()) {
124130             comp.stopAnimation();
124131         }
124132
124133         // If the Component is fully floated when they click the placeholder Tool,
124134         // it will be primed with a slide out animation object... so delete that
124135         // and remove the mouseout listeners
124136         if (comp.slideOutAnim) {
124137             // Remove mouse leave monitors
124138             compEl.un(comp.panelMouseMon);
124139             placeholderEl.un(comp.placeholderMouseMon);
124140
124141             delete comp.slideOutAnim;
124142             delete comp.panelMouseMon;
124143             delete comp.placeholderMouseMon;
124144
124145             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
124146             isFloating = true;
124147         }
124148
124149         // Do not trigger a layout during transition to expanded Component
124150         me.owner.suspendLayout = true;
124151         shadowContainer.suspendLayout = true;
124152
124153         // Prevent upward notifications from downstream layouts
124154         shadowLayout.layoutBusy = true;
124155         if (shadowContainer.componentLayout) {
124156             shadowContainer.componentLayout.layoutBusy = true;
124157         }
124158         me.shadowContainer.layout.layoutBusy = true;
124159         me.layoutBusy = true;
124160         me.owner.componentLayout.layoutBusy = true;
124161
124162         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
124163         // Find where the shadow Box layout plans to put the expanding Component.
124164         comp.hidden = false;
124165         comp.collapsed = false;
124166         if (hidePlaceholder) {
124167             placeholder.hidden = true;
124168         }
124169         toCompBox = shadowLayout.calculateChildBox(comp);
124170
124171         // Show the collapse tool in case it was hidden by the slide-in
124172         if (comp.collapseTool) {
124173             comp.collapseTool.show();
124174         }
124175
124176         // If we're going to animate, we need to hide the component before moving it back into position
124177         if (comp.animCollapse && !isFloating) {
124178             compEl.setStyle('visibility', 'hidden');
124179         }
124180         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124181
124182         // Equalize the size of the expanding Component prior to animation
124183         // in case the layout area has changed size during the time it was collapsed.
124184         curSize = comp.getSize();
124185         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124186             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124187         }
124188
124189         // Jobs to be done after the expand has been done
124190         function afterExpand() {
124191             // Reinstate automatic laying out.
124192             me.owner.suspendLayout = sl;
124193             shadowContainer.suspendLayout = scsl;
124194             delete shadowLayout.layoutBusy;
124195             if (shadowContainer.componentLayout) {
124196                 delete shadowContainer.componentLayout.layoutBusy;
124197             }
124198             delete me.shadowContainer.layout.layoutBusy;
124199             delete me.layoutBusy;
124200             delete me.owner.componentLayout.layoutBusy;
124201             delete comp.collapsedChangingLayout;
124202
124203             // In case it was floated out and they clicked the re-expand tool
124204             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124205
124206             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
124207             comp.fireEvent('expand', comp);
124208         }
124209
124210         // Hide the placeholder
124211         if (hidePlaceholder) {
124212             placeholder.el.hide();
124213         }
124214
124215         // Slide the expanding Component to its new position.
124216         // When that is done, layout the layout.
124217         if (comp.animCollapse && !isFloating) {
124218             compEl.dom.style.zIndex = 100;
124219             compEl.slideIn(me.slideDirection[comp.region], {
124220                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124221                 listeners: {
124222                     afteranimate: function() {
124223                         compEl.dom.style.zIndex = '';
124224                         comp.hidden = false;
124225                         shadowLayout.onLayout();
124226                         afterExpand();
124227                     }
124228                 }
124229             });
124230         } else {
124231             shadowLayout.onLayout();
124232             afterExpand();
124233         }
124234     },
124235
124236     floatCollapsedPanel: function(e, comp) {
124237
124238         if (comp.floatable === false) {
124239             return;
124240         }
124241
124242         var me = this,
124243             compEl = comp.el,
124244             placeholder = comp.placeholder,
124245             placeholderEl = placeholder.el,
124246             shadowContainer = comp.shadowOwnerCt,
124247             shadowLayout = shadowContainer.layout,
124248             placeholderBox = shadowLayout.getChildBox(placeholder),
124249             scsl = shadowContainer.suspendLayout,
124250             curSize, toCompBox, compAnim;
124251
124252         // Ignore clicks on tools.
124253         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
124254             return;
124255         }
124256
124257         // It's *being* animated, ignore the click.
124258         // Possible future enhancement: Stop and *reverse* the current active Fx.
124259         if (compEl.getActiveAnimation()) {
124260             return;
124261         }
124262
124263         // If the Component is already fully floated when they click the placeholder,
124264         // it will be primed with a slide out animation object... so slide it out.
124265         if (comp.slideOutAnim) {
124266             me.slideOutFloatedComponent(comp);
124267             return;
124268         }
124269
124270         // Function to be called when the mouse leaves the floated Panel
124271         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
124272         function onMouseLeaveFloated(e) {
124273             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
124274
124275             // If mouse is not within slide Region, slide it out
124276             if (!slideRegion.contains(e.getPoint())) {
124277                 me.slideOutFloatedComponent(comp);
124278             }
124279         }
124280
124281         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
124282         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
124283
124284         // Do not trigger a layout during slide out of the Component
124285         shadowContainer.suspendLayout = true;
124286
124287         // Prevent upward notifications from downstream layouts
124288         me.layoutBusy = true;
124289         me.owner.componentLayout.layoutBusy = true;
124290
124291         // The collapse tool is hidden while slid.
124292         // It is re-shown on expand.
124293         if (comp.collapseTool) {
124294             comp.collapseTool.hide();
124295         }
124296
124297         // Set flags so that the layout will calculate the boxes for what we want
124298         comp.hidden = false;
124299         comp.collapsed = false;
124300         placeholder.hidden = true;
124301
124302         // Recalculate new arrangement of the Component being floated.
124303         toCompBox = shadowLayout.calculateChildBox(comp);
124304         placeholder.hidden = false;
124305
124306         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
124307         if (comp.region == 'north' || comp.region == 'west') {
124308             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
124309         } else {
124310             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
124311         }
124312         compEl.setStyle('visibility', 'hidden');
124313         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124314
124315         // Equalize the size of the expanding Component prior to animation
124316         // in case the layout area has changed size during the time it was collapsed.
124317         curSize = comp.getSize();
124318         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124319             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124320         }
124321
124322         // This animation slides the collapsed Component's el out to just beyond its placeholder
124323         compAnim = {
124324             listeners: {
124325                 afteranimate: function() {
124326                     shadowContainer.suspendLayout = scsl;
124327                     delete me.layoutBusy;
124328                     delete me.owner.componentLayout.layoutBusy;
124329
124330                     // Prime the Component with an Anim config object to slide it back out
124331                     compAnim.listeners = {
124332                         afterAnimate: function() {
124333                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
124334
124335                             // Reinstate the correct, current state after slide out animation finishes
124336                             comp.hidden = true;
124337                             comp.collapsed = true;
124338                             delete comp.slideOutAnim;
124339                             delete comp.panelMouseMon;
124340                             delete comp.placeholderMouseMon;
124341                         }
124342                     };
124343                     comp.slideOutAnim = compAnim;
124344                 }
124345             },
124346             duration: 500
124347         };
124348
124349         // Give the element the correct class which places it at a high z-index
124350         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124351
124352         // Begin the slide in
124353         compEl.slideIn(me.slideDirection[comp.region], compAnim);
124354
124355         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
124356         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
124357
124358     },
124359
124360     slideOutFloatedComponent: function(comp) {
124361         var compEl = comp.el,
124362             slideOutAnim;
124363
124364         // Remove mouse leave monitors
124365         compEl.un(comp.panelMouseMon);
124366         comp.placeholder.el.un(comp.placeholderMouseMon);
124367
124368         // Slide the Component out
124369         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
124370
124371         delete comp.slideOutAnim;
124372         delete comp.panelMouseMon;
124373         delete comp.placeholderMouseMon;
124374     },
124375
124376     /*
124377      * @private
124378      * Ensure any collapsed placeholder Component is destroyed along with its region.
124379      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
124380      */
124381     onRegionDestroy: function(comp) {
124382         var placeholder = comp.placeholder;
124383         if (placeholder) {
124384             delete placeholder.ownerCt;
124385             placeholder.destroy();
124386         }
124387     },
124388
124389     /*
124390      * @private
124391      * Ensure any shadow Containers are destroyed.
124392      * Ensure we don't keep references to Components.
124393      */
124394     onDestroy: function() {
124395         var me = this,
124396             shadowContainer = me.shadowContainer,
124397             embeddedContainer = me.embeddedContainer;
124398
124399         if (shadowContainer) {
124400             delete shadowContainer.ownerCt;
124401             Ext.destroy(shadowContainer);
124402         }
124403
124404         if (embeddedContainer) {
124405             delete embeddedContainer.ownerCt;
124406             Ext.destroy(embeddedContainer);
124407         }
124408         delete me.regions;
124409         delete me.splitters;
124410         delete me.shadowContainer;
124411         delete me.embeddedContainer;
124412         me.callParent(arguments);
124413     }
124414 });
124415
124416 /**
124417  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
124418  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
124419  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
124420  * and should generally not need to be created directly via the new keyword.
124421  *
124422  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
124423  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
124424  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
124425  * so that functionality must be provided by the developer.
124426  *
124427  * To change the active card of a container, call the setActiveItem method of its layout:
124428  *
124429  *     Ext.create('Ext.panel.Panel', {
124430  *         layout: 'card',
124431  *         items: [
124432  *             { html: 'Card 1' },
124433  *             { html: 'Card 2' }
124434  *         ],
124435  *         renderTo: Ext.getBody()
124436  *     });
124437  *
124438  *     p.getLayout().setActiveItem(1);
124439  *
124440  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
124441  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
124442  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
124443  * completely different implementation.  For serious implementations, a better approach would be to extend
124444  * CardLayout to provide the custom functionality needed.
124445  *
124446  *     @example
124447  *     var navigate = function(panel, direction){
124448  *         // This routine could contain business logic required to manage the navigation steps.
124449  *         // It would call setActiveItem as needed, manage navigation button state, handle any
124450  *         // branching logic that might be required, handle alternate actions like cancellation
124451  *         // or finalization, etc.  A complete wizard implementation could get pretty
124452  *         // sophisticated depending on the complexity required, and should probably be
124453  *         // done as a subclass of CardLayout in a real-world implementation.
124454  *         var layout = panel.getLayout();
124455  *         layout[direction]();
124456  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
124457  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
124458  *     };
124459  *
124460  *     Ext.create('Ext.panel.Panel', {
124461  *         title: 'Example Wizard',
124462  *         width: 300,
124463  *         height: 200,
124464  *         layout: 'card',
124465  *         bodyStyle: 'padding:15px',
124466  *         defaults: {
124467  *             // applied to each contained panel
124468  *             border: false
124469  *         },
124470  *         // just an example of one possible navigation scheme, using buttons
124471  *         bbar: [
124472  *             {
124473  *                 id: 'move-prev',
124474  *                 text: 'Back',
124475  *                 handler: function(btn) {
124476  *                     navigate(btn.up("panel"), "prev");
124477  *                 },
124478  *                 disabled: true
124479  *             },
124480  *             '->', // greedy spacer so that the buttons are aligned to each side
124481  *             {
124482  *                 id: 'move-next',
124483  *                 text: 'Next',
124484  *                 handler: function(btn) {
124485  *                     navigate(btn.up("panel"), "next");
124486  *                 }
124487  *             }
124488  *         ],
124489  *         // the panels (or "cards") within the layout
124490  *         items: [{
124491  *             id: 'card-0',
124492  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
124493  *         },{
124494  *             id: 'card-1',
124495  *             html: '<p>Step 2 of 3</p>'
124496  *         },{
124497  *             id: 'card-2',
124498  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
124499  *         }],
124500  *         renderTo: Ext.getBody()
124501  *     });
124502  */
124503 Ext.define('Ext.layout.container.Card', {
124504
124505     /* Begin Definitions */
124506
124507     alias: ['layout.card'],
124508     alternateClassName: 'Ext.layout.CardLayout',
124509
124510     extend: 'Ext.layout.container.AbstractCard',
124511
124512     /* End Definitions */
124513
124514     /**
124515      * Makes the given card active.
124516      *
124517      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
124518      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
124519      *     var panel = Ext.create('Ext.panel.Panel', {
124520      *         layout: 'card',
124521      *         activeItem: 0,
124522      *         items: [card1, card2]
124523      *     });
124524      *     // These are all equivalent
124525      *     panel.getLayout().setActiveItem(card2);
124526      *     panel.getLayout().setActiveItem('card-2');
124527      *     panel.getLayout().setActiveItem(1);
124528      *
124529      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
124530      * {@link Ext.Component#itemId itemId}, or index of component.
124531      * @return {Ext.Component} the activated component or false when nothing activated.
124532      * False is returned also when trying to activate an already active card.
124533      */
124534     setActiveItem: function(newCard) {
124535         var me = this,
124536             owner = me.owner,
124537             oldCard = me.activeItem,
124538             newIndex;
124539
124540         newCard = me.parseActiveItem(newCard);
124541         newIndex = owner.items.indexOf(newCard);
124542
124543         // If the card is not a child of the owner, then add it
124544         if (newIndex == -1) {
124545             newIndex = owner.items.items.length;
124546             owner.add(newCard);
124547         }
124548
124549         // Is this a valid, different card?
124550         if (newCard && oldCard != newCard) {
124551             // If the card has not been rendered yet, now is the time to do so.
124552             if (!newCard.rendered) {
124553                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
124554                 me.configureItem(newCard, 0);
124555             }
124556
124557             me.activeItem = newCard;
124558
124559             // Fire the beforeactivate and beforedeactivate events on the cards
124560             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
124561                 return false;
124562             }
124563             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
124564                 return false;
124565             }
124566
124567             // If the card hasnt been sized yet, do it now
124568             if (me.sizeAllCards) {
124569                 // onLayout calls setItemBox
124570                 me.onLayout();
124571             }
124572             else {
124573                 me.setItemBox(newCard, me.getTargetBox());
124574             }
124575
124576             me.owner.suspendLayout = true;
124577
124578             if (oldCard) {
124579                 if (me.hideInactive) {
124580                     oldCard.hide();
124581                 }
124582                 oldCard.fireEvent('deactivate', oldCard, newCard);
124583             }
124584
124585             // Make sure the new card is shown
124586             me.owner.suspendLayout = false;
124587             if (newCard.hidden) {
124588                 newCard.show();
124589             } else {
124590                 me.onLayout();
124591             }
124592
124593             newCard.fireEvent('activate', newCard, oldCard);
124594
124595             return newCard;
124596         }
124597         return false;
124598     },
124599
124600     configureItem: function(item) {
124601         // Card layout only controls dimensions which IT has controlled.
124602         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
124603         item.layoutManagedHeight = 0;
124604         item.layoutManagedWidth = 0;
124605
124606         this.callParent(arguments);
124607     }});
124608 /**
124609  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
124610  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
124611  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
124612  * and should generally not need to be created directly via the new keyword.
124613  *
124614  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
124615  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
124616  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
124617  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
124618  *
124619  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
124620  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
124621  *
124622  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
124623  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
124624  * are skipped, but their widths are subtracted from the overall container width.
124625  *
124626  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
124627  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
124628  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
124629  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
124630  * together, otherwise your layout may not render as expected.
124631  *
124632  *     @example
124633  *     // All columns are percentages -- they must add up to 1
124634  *     Ext.create('Ext.panel.Panel', {
124635  *         title: 'Column Layout - Percentage Only',
124636  *         width: 350,
124637  *         height: 250,
124638  *         layout:'column',
124639  *         items: [{
124640  *             title: 'Column 1',
124641  *             columnWidth: .25
124642  *         },{
124643  *             title: 'Column 2',
124644  *             columnWidth: .55
124645  *         },{
124646  *             title: 'Column 3',
124647  *             columnWidth: .20
124648  *         }],
124649  *         renderTo: Ext.getBody()
124650  *     });
124651  *
124652  *     // Mix of width and columnWidth -- all columnWidth values must add up
124653  *     // to 1. The first column will take up exactly 120px, and the last two
124654  *     // columns will fill the remaining container width.
124655  *
124656  *     Ext.create('Ext.Panel', {
124657  *         title: 'Column Layout - Mixed',
124658  *         width: 350,
124659  *         height: 250,
124660  *         layout:'column',
124661  *         items: [{
124662  *             title: 'Column 1',
124663  *             width: 120
124664  *         },{
124665  *             title: 'Column 2',
124666  *             columnWidth: .7
124667  *         },{
124668  *             title: 'Column 3',
124669  *             columnWidth: .3
124670  *         }],
124671  *         renderTo: Ext.getBody()
124672  *     });
124673  */
124674 Ext.define('Ext.layout.container.Column', {
124675
124676     extend: 'Ext.layout.container.Auto',
124677     alias: ['layout.column'],
124678     alternateClassName: 'Ext.layout.ColumnLayout',
124679
124680     type: 'column',
124681
124682     itemCls: Ext.baseCSSPrefix + 'column',
124683
124684     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
124685
124686     scrollOffset: 0,
124687
124688     bindToOwnerCtComponent: false,
124689
124690     getRenderTarget : function() {
124691         if (!this.innerCt) {
124692
124693             // the innerCt prevents wrapping and shuffling while
124694             // the container is resizing
124695             this.innerCt = this.getTarget().createChild({
124696                 cls: Ext.baseCSSPrefix + 'column-inner'
124697             });
124698
124699             // Column layout uses natural HTML flow to arrange the child items.
124700             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
124701             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
124702             this.clearEl = this.innerCt.createChild({
124703                 cls: Ext.baseCSSPrefix + 'clear',
124704                 role: 'presentation'
124705             });
124706         }
124707         return this.innerCt;
124708     },
124709
124710     // private
124711     onLayout : function() {
124712         var me = this,
124713             target = me.getTarget(),
124714             items = me.getLayoutItems(),
124715             len = items.length,
124716             item,
124717             i,
124718             parallelMargins = [],
124719             itemParallelMargins,
124720             size,
124721             availableWidth,
124722             columnWidth;
124723
124724         size = me.getLayoutTargetSize();
124725         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
124726             return;
124727         }
124728
124729         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
124730         // If, after the layout, it is detected that there is vertical overflow,
124731         // we will recurse back through here. Do not adjust overflow style at that time.
124732         if (me.adjustmentPass) {
124733             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124734                 size.width = me.adjustedWidth;
124735             }
124736         } else {
124737             i = target.getStyle('overflow');
124738             if (i && i != 'hidden') {
124739                 me.autoScroll = true;
124740                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
124741                     target.setStyle('overflow', 'hidden');
124742                     size = me.getLayoutTargetSize();
124743                 }
124744             }
124745         }
124746
124747         availableWidth = size.width - me.scrollOffset;
124748         me.innerCt.setWidth(availableWidth);
124749
124750         // some columns can be percentages while others are fixed
124751         // so we need to make 2 passes
124752         for (i = 0; i < len; i++) {
124753             item = items[i];
124754             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
124755             if (!item.columnWidth) {
124756                 availableWidth -= (item.getWidth() + itemParallelMargins);
124757             }
124758         }
124759
124760         availableWidth = availableWidth < 0 ? 0 : availableWidth;
124761         for (i = 0; i < len; i++) {
124762             item = items[i];
124763             if (item.columnWidth) {
124764                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
124765                 me.setItemSize(item, columnWidth, item.height);
124766             } else {
124767                 me.layoutItem(item);
124768             }
124769         }
124770
124771         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
124772         if (!me.adjustmentPass && me.autoScroll) {
124773
124774             // If there's a vertical overflow, relay with scrollbars
124775             target.setStyle('overflow', 'auto');
124776             me.adjustmentPass = (target.dom.scrollHeight > size.height);
124777             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124778                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
124779             } else {
124780                 target.setStyle('overflow', 'auto');
124781             }
124782
124783             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
124784             if (me.adjustmentPass) {
124785                 me.onLayout();
124786             }
124787         }
124788         delete me.adjustmentPass;
124789     },
124790
124791     configureItem: function(item) {
124792         this.callParent(arguments);
124793
124794         if (item.columnWidth) {
124795             item.layoutManagedWidth = 1;
124796         }
124797     }
124798 });
124799 /**
124800  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
124801  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
124802  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
124803  * need to be created directly via the new keyword.
124804  *
124805  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
124806  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
124807  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
124808  * added to a TableLayout can supply the following table-specific config properties:
124809  *
124810  *   - **rowspan** Applied to the table cell containing the item.
124811  *   - **colspan** Applied to the table cell containing the item.
124812  *   - **cellId** An id applied to the table cell containing the item.
124813  *   - **cellCls** A CSS class name added to the table cell containing the item.
124814  *
124815  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
124816  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
124817  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
124818  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
124819  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
124820  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
124821  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
124822  * end up with missing and/or extra cells! Example usage:
124823  *
124824  *     @example
124825  *     Ext.create('Ext.panel.Panel', {
124826  *         title: 'Table Layout',
124827  *         width: 300,
124828  *         height: 150,
124829  *         layout: {
124830  *             type: 'table',
124831  *             // The total column count must be specified here
124832  *             columns: 3
124833  *         },
124834  *         defaults: {
124835  *             // applied to each contained panel
124836  *             bodyStyle: 'padding:20px'
124837  *         },
124838  *         items: [{
124839  *             html: 'Cell A content',
124840  *             rowspan: 2
124841  *         },{
124842  *             html: 'Cell B content',
124843  *             colspan: 2
124844  *         },{
124845  *             html: 'Cell C content',
124846  *             cellCls: 'highlight'
124847  *         },{
124848  *             html: 'Cell D content'
124849  *         }],
124850  *         renderTo: Ext.getBody()
124851  *     });
124852  */
124853 Ext.define('Ext.layout.container.Table', {
124854
124855     /* Begin Definitions */
124856
124857     alias: ['layout.table'],
124858     extend: 'Ext.layout.container.Auto',
124859     alternateClassName: 'Ext.layout.TableLayout',
124860
124861     /* End Definitions */
124862
124863     /**
124864      * @cfg {Number} columns
124865      * The total number of columns to create in the table for this layout. If not specified, all Components added to
124866      * this layout will be rendered into a single row using one column per Component.
124867      */
124868
124869     // private
124870     monitorResize:false,
124871
124872     type: 'table',
124873
124874     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
124875     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
124876     autoSize: true,
124877
124878     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
124879
124880     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
124881     tableCls: Ext.baseCSSPrefix + 'table-layout',
124882     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
124883
124884     /**
124885      * @cfg {Object} tableAttrs
124886      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124887      * create the layout's `<table>` element. Example:
124888      *
124889      *     {
124890      *         xtype: 'panel',
124891      *         layout: {
124892      *             type: 'table',
124893      *             columns: 3,
124894      *             tableAttrs: {
124895      *                 style: {
124896      *                     width: '100%'
124897      *                 }
124898      *             }
124899      *         }
124900      *     }
124901      */
124902     tableAttrs:null,
124903
124904     /**
124905      * @cfg {Object} trAttrs
124906      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124907      * create the layout's <tr> elements.
124908      */
124909
124910     /**
124911      * @cfg {Object} tdAttrs
124912      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124913      * create the layout's <td> elements.
124914      */
124915
124916     /**
124917      * @private
124918      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
124919      * location in the table structure.
124920      */
124921     renderItems: function(items) {
124922         var tbody = this.getTable().tBodies[0],
124923             rows = tbody.rows,
124924             i = 0,
124925             len = items.length,
124926             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
124927
124928         // Calculate the correct cell structure for the current items
124929         cells = this.calculateCells(items);
124930
124931         // Loop over each cell and compare to the current cells in the table, inserting/
124932         // removing/moving cells as needed, and making sure each item is rendered into
124933         // the correct cell.
124934         for (; i < len; i++) {
124935             curCell = cells[i];
124936             rowIdx = curCell.rowIdx;
124937             cellIdx = curCell.cellIdx;
124938             item = items[i];
124939
124940             // If no row present, create and insert one
124941             trEl = rows[rowIdx];
124942             if (!trEl) {
124943                 trEl = tbody.insertRow(rowIdx);
124944                 if (this.trAttrs) {
124945                     trEl.set(this.trAttrs);
124946                 }
124947             }
124948
124949             // If no cell present, create and insert one
124950             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
124951             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
124952                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
124953                 itemCt.setWidth(null);
124954             }
124955
124956             // Render or move the component into the cell
124957             if (!item.rendered) {
124958                 this.renderItem(item, itemCt, 0);
124959             }
124960             else if (!this.isValidParent(item, itemCt, 0)) {
124961                 this.moveItem(item, itemCt, 0);
124962             }
124963
124964             // Set the cell properties
124965             if (this.tdAttrs) {
124966                 tdEl.set(this.tdAttrs);
124967             }
124968             tdEl.set({
124969                 colSpan: item.colspan || 1,
124970                 rowSpan: item.rowspan || 1,
124971                 id: item.cellId || '',
124972                 cls: this.cellCls + ' ' + (item.cellCls || '')
124973             });
124974
124975             // If at the end of a row, remove any extra cells
124976             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
124977                 cellIdx++;
124978                 while (trEl.cells[cellIdx]) {
124979                     trEl.deleteCell(cellIdx);
124980                 }
124981             }
124982         }
124983
124984         // Delete any extra rows
124985         rowIdx++;
124986         while (tbody.rows[rowIdx]) {
124987             tbody.deleteRow(rowIdx);
124988         }
124989     },
124990
124991     afterLayout: function() {
124992         this.callParent();
124993
124994         if (this.needsDivWrap()) {
124995             // set wrapper div width to match layed out item - see docs below
124996             Ext.Array.forEach(this.getLayoutItems(), function(item) {
124997                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
124998             });
124999         }
125000     },
125001
125002     /**
125003      * @private
125004      * Determine the row and cell indexes for each component, taking into consideration
125005      * the number of columns and each item's configured colspan/rowspan values.
125006      * @param {Array} items The layout components
125007      * @return {Object[]} List of row and cell indexes for each of the components
125008      */
125009     calculateCells: function(items) {
125010         var cells = [],
125011             rowIdx = 0,
125012             colIdx = 0,
125013             cellIdx = 0,
125014             totalCols = this.columns || Infinity,
125015             rowspans = [], //rolling list of active rowspans for each column
125016             i = 0, j,
125017             len = items.length,
125018             item;
125019
125020         for (; i < len; i++) {
125021             item = items[i];
125022
125023             // Find the first available row/col slot not taken up by a spanning cell
125024             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
125025                 if (colIdx >= totalCols) {
125026                     // move down to next row
125027                     colIdx = 0;
125028                     cellIdx = 0;
125029                     rowIdx++;
125030
125031                     // decrement all rowspans
125032                     for (j = 0; j < totalCols; j++) {
125033                         if (rowspans[j] > 0) {
125034                             rowspans[j]--;
125035                         }
125036                     }
125037                 } else {
125038                     colIdx++;
125039                 }
125040             }
125041
125042             // Add the cell info to the list
125043             cells.push({
125044                 rowIdx: rowIdx,
125045                 cellIdx: cellIdx
125046             });
125047
125048             // Increment
125049             for (j = item.colspan || 1; j; --j) {
125050                 rowspans[colIdx] = item.rowspan || 1;
125051                 ++colIdx;
125052             }
125053             ++cellIdx;
125054         }
125055
125056         return cells;
125057     },
125058
125059     /**
125060      * @private
125061      * Return the layout's table element, creating it if necessary.
125062      */
125063     getTable: function() {
125064         var table = this.table;
125065         if (!table) {
125066             table = this.table = this.getTarget().createChild(
125067                 Ext.apply({
125068                     tag: 'table',
125069                     role: 'presentation',
125070                     cls: this.tableCls,
125071                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
125072                     cn: {tag: 'tbody'}
125073                 }, this.tableAttrs),
125074                 null, true
125075             );
125076         }
125077         return table;
125078     },
125079
125080     /**
125081      * @private
125082      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
125083      * will include that padding in the size of the cell, making it always larger than the
125084      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
125085      * and then set that div's width to match the item rendered within it afterLayout. This method
125086      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
125087      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
125088      */
125089     needsDivWrap: function() {
125090         return Ext.isOpera10_5;
125091     }
125092 });
125093 /**
125094  * A base class for all menu items that require menu-related functionality such as click handling,
125095  * sub-menus, icons, etc.
125096  *
125097  *     @example
125098  *     Ext.create('Ext.menu.Menu', {
125099  *         width: 100,
125100  *         height: 100,
125101  *         floating: false,  // usually you want this set to True (default)
125102  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125103  *         items: [{
125104  *             text: 'icon item',
125105  *             iconCls: 'add16'
125106  *         },{
125107  *             text: 'text item'
125108  *         },{
125109  *             text: 'plain item',
125110  *             plain: true
125111  *         }]
125112  *     });
125113  */
125114 Ext.define('Ext.menu.Item', {
125115     extend: 'Ext.Component',
125116     alias: 'widget.menuitem',
125117     alternateClassName: 'Ext.menu.TextItem',
125118
125119     /**
125120      * @property {Boolean} activated
125121      * Whether or not this item is currently activated
125122      */
125123
125124     /**
125125      * @property {Ext.menu.Menu} parentMenu
125126      * The parent Menu of this item.
125127      */
125128
125129     /**
125130      * @cfg {String} activeCls
125131      * The CSS class added to the menu item when the item is activated (focused/mouseover).
125132      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
125133      */
125134     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
125135
125136     /**
125137      * @cfg {String} ariaRole @hide
125138      */
125139     ariaRole: 'menuitem',
125140
125141     /**
125142      * @cfg {Boolean} canActivate
125143      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
125144      */
125145     canActivate: true,
125146
125147     /**
125148      * @cfg {Number} clickHideDelay
125149      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
125150      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
125151      */
125152     clickHideDelay: 1,
125153
125154     /**
125155      * @cfg {Boolean} destroyMenu
125156      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
125157      */
125158     destroyMenu: true,
125159
125160     /**
125161      * @cfg {String} disabledCls
125162      * The CSS class added to the menu item when the item is disabled.
125163      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
125164      */
125165     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
125166
125167     /**
125168      * @cfg {String} href
125169      * The href attribute to use for the underlying anchor link. Defaults to `#`.
125170      * @markdown
125171      */
125172
125173      /**
125174       * @cfg {String} hrefTarget
125175       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
125176       * @markdown
125177       */
125178
125179     /**
125180      * @cfg {Boolean} hideOnClick
125181      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
125182      * @markdown
125183      */
125184     hideOnClick: true,
125185
125186     /**
125187      * @cfg {String} icon
125188      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
125189      * @markdown
125190      */
125191
125192     /**
125193      * @cfg {String} iconCls
125194      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
125195      * @markdown
125196      */
125197
125198     isMenuItem: true,
125199
125200     /**
125201      * @cfg {Mixed} menu
125202      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
125203      * which will act as a sub-menu to this item.
125204      * @markdown
125205      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
125206      */
125207
125208     /**
125209      * @cfg {String} menuAlign
125210      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
125211      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
125212      * @markdown
125213      */
125214     menuAlign: 'tl-tr?',
125215
125216     /**
125217      * @cfg {Number} menuExpandDelay
125218      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
125219      * @markdown
125220      */
125221     menuExpandDelay: 200,
125222
125223     /**
125224      * @cfg {Number} menuHideDelay
125225      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
125226      * @markdown
125227      */
125228     menuHideDelay: 200,
125229
125230     /**
125231      * @cfg {Boolean} plain
125232      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
125233      * @markdown
125234      */
125235
125236     renderTpl: [
125237         '<tpl if="plain">',
125238             '{text}',
125239         '</tpl>',
125240         '<tpl if="!plain">',
125241             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
125242                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
125243                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
125244                 '<tpl if="menu">',
125245                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
125246                 '</tpl>',
125247             '</a>',
125248         '</tpl>'
125249     ],
125250
125251     maskOnDisable: false,
125252
125253     /**
125254      * @cfg {String} text
125255      * The text/html to display in this item. Defaults to `undefined`.
125256      * @markdown
125257      */
125258
125259     activate: function() {
125260         var me = this;
125261
125262         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
125263             me.el.addCls(me.activeCls);
125264             me.focus();
125265             me.activated = true;
125266             me.fireEvent('activate', me);
125267         }
125268     },
125269
125270     blur: function() {
125271         this.$focused = false;
125272         this.callParent(arguments);
125273     },
125274
125275     deactivate: function() {
125276         var me = this;
125277
125278         if (me.activated) {
125279             me.el.removeCls(me.activeCls);
125280             me.blur();
125281             me.hideMenu();
125282             me.activated = false;
125283             me.fireEvent('deactivate', me);
125284         }
125285     },
125286
125287     deferExpandMenu: function() {
125288         var me = this;
125289
125290         if (!me.menu.rendered || !me.menu.isVisible()) {
125291             me.parentMenu.activeChild = me.menu;
125292             me.menu.parentItem = me;
125293             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
125294             me.menu.showBy(me, me.menuAlign);
125295         }
125296     },
125297
125298     deferHideMenu: function() {
125299         if (this.menu.isVisible()) {
125300             this.menu.hide();
125301         }
125302     },
125303
125304     deferHideParentMenus: function() {
125305         Ext.menu.Manager.hideAll();
125306     },
125307
125308     expandMenu: function(delay) {
125309         var me = this;
125310
125311         if (me.menu) {
125312             clearTimeout(me.hideMenuTimer);
125313             if (delay === 0) {
125314                 me.deferExpandMenu();
125315             } else {
125316                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
125317             }
125318         }
125319     },
125320
125321     focus: function() {
125322         this.$focused = true;
125323         this.callParent(arguments);
125324     },
125325
125326     getRefItems: function(deep){
125327         var menu = this.menu,
125328             items;
125329
125330         if (menu) {
125331             items = menu.getRefItems(deep);
125332             items.unshift(menu);
125333         }
125334         return items || [];
125335     },
125336
125337     hideMenu: function(delay) {
125338         var me = this;
125339
125340         if (me.menu) {
125341             clearTimeout(me.expandMenuTimer);
125342             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
125343         }
125344     },
125345
125346     initComponent: function() {
125347         var me = this,
125348             prefix = Ext.baseCSSPrefix,
125349             cls = [prefix + 'menu-item'];
125350
125351         me.addEvents(
125352             /**
125353              * @event activate
125354              * Fires when this item is activated
125355              * @param {Ext.menu.Item} item The activated item
125356              */
125357             'activate',
125358
125359             /**
125360              * @event click
125361              * Fires when this item is clicked
125362              * @param {Ext.menu.Item} item The item that was clicked
125363              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
125364              */
125365             'click',
125366
125367             /**
125368              * @event deactivate
125369              * Fires when this tiem is deactivated
125370              * @param {Ext.menu.Item} item The deactivated item
125371              */
125372             'deactivate'
125373         );
125374
125375         if (me.plain) {
125376             cls.push(prefix + 'menu-item-plain');
125377         }
125378
125379         if (me.cls) {
125380             cls.push(me.cls);
125381         }
125382
125383         me.cls = cls.join(' ');
125384
125385         if (me.menu) {
125386             me.menu = Ext.menu.Manager.get(me.menu);
125387         }
125388
125389         me.callParent(arguments);
125390     },
125391
125392     onClick: function(e) {
125393         var me = this;
125394
125395         if (!me.href) {
125396             e.stopEvent();
125397         }
125398
125399         if (me.disabled) {
125400             return;
125401         }
125402
125403         if (me.hideOnClick) {
125404             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
125405         }
125406
125407         Ext.callback(me.handler, me.scope || me, [me, e]);
125408         me.fireEvent('click', me, e);
125409
125410         if (!me.hideOnClick) {
125411             me.focus();
125412         }
125413     },
125414
125415     onDestroy: function() {
125416         var me = this;
125417
125418         clearTimeout(me.expandMenuTimer);
125419         clearTimeout(me.hideMenuTimer);
125420         clearTimeout(me.deferHideParentMenusTimer);
125421
125422         if (me.menu) {
125423             delete me.menu.parentItem;
125424             delete me.menu.parentMenu;
125425             delete me.menu.ownerCt;
125426             if (me.destroyMenu !== false) {
125427                 me.menu.destroy();
125428             }
125429         }
125430         me.callParent(arguments);
125431     },
125432
125433     onRender: function(ct, pos) {
125434         var me = this,
125435             blank = Ext.BLANK_IMAGE_URL;
125436
125437         Ext.applyIf(me.renderData, {
125438             href: me.href || '#',
125439             hrefTarget: me.hrefTarget,
125440             icon: me.icon || blank,
125441             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
125442             menu: Ext.isDefined(me.menu),
125443             plain: me.plain,
125444             text: me.text,
125445             blank: blank
125446         });
125447
125448         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
125449
125450         me.callParent(arguments);
125451     },
125452
125453     /**
125454      * Sets the {@link #click} handler of this item
125455      * @param {Function} fn The handler function
125456      * @param {Object} scope (optional) The scope of the handler function
125457      */
125458     setHandler: function(fn, scope) {
125459         this.handler = fn || null;
125460         this.scope = scope;
125461     },
125462
125463     /**
125464      * Sets the {@link #iconCls} of this item
125465      * @param {String} iconCls The CSS class to set to {@link #iconCls}
125466      */
125467     setIconCls: function(iconCls) {
125468         var me = this;
125469
125470         if (me.iconEl) {
125471             if (me.iconCls) {
125472                 me.iconEl.removeCls(me.iconCls);
125473             }
125474
125475             if (iconCls) {
125476                 me.iconEl.addCls(iconCls);
125477             }
125478         }
125479
125480         me.iconCls = iconCls;
125481     },
125482
125483     /**
125484      * Sets the {@link #text} of this item
125485      * @param {String} text The {@link #text}
125486      */
125487     setText: function(text) {
125488         var me = this,
125489             el = me.textEl || me.el;
125490
125491         me.text = text;
125492
125493         if (me.rendered) {
125494             el.update(text || '');
125495             // cannot just call doComponentLayout due to stretchmax
125496             me.ownerCt.redoComponentLayout();
125497         }
125498     }
125499 });
125500
125501 /**
125502  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
125503  *
125504  *     @example
125505  *     Ext.create('Ext.menu.Menu', {
125506  *         width: 100,
125507  *         height: 110,
125508  *         floating: false,  // usually you want this set to True (default)
125509  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125510  *         items: [{
125511  *             xtype: 'menucheckitem',
125512  *             text: 'select all'
125513  *         },{
125514  *             xtype: 'menucheckitem',
125515  *             text: 'select specific',
125516  *         },{
125517  *             iconCls: 'add16',
125518  *             text: 'icon item'
125519  *         },{
125520  *             text: 'regular item'
125521  *         }]
125522  *     });
125523  */
125524 Ext.define('Ext.menu.CheckItem', {
125525     extend: 'Ext.menu.Item',
125526     alias: 'widget.menucheckitem',
125527
125528     /**
125529      * @cfg {String} checkedCls
125530      * The CSS class used by {@link #cls} to show the checked state.
125531      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
125532      */
125533     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
125534     /**
125535      * @cfg {String} uncheckedCls
125536      * The CSS class used by {@link #cls} to show the unchecked state.
125537      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
125538      */
125539     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
125540     /**
125541      * @cfg {String} groupCls
125542      * The CSS class applied to this item's icon image to denote being a part of a radio group.
125543      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
125544      * Any specified {@link #iconCls} overrides this.
125545      */
125546     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
125547
125548     /**
125549      * @cfg {Boolean} hideOnClick
125550      * Whether to not to hide the owning menu when this item is clicked.
125551      * Defaults to `false` for checkbox items, and to `true` for radio group items.
125552      */
125553     hideOnClick: false,
125554
125555     afterRender: function() {
125556         var me = this;
125557         this.callParent();
125558         me.checked = !me.checked;
125559         me.setChecked(!me.checked, true);
125560     },
125561
125562     initComponent: function() {
125563         var me = this;
125564         me.addEvents(
125565             /**
125566              * @event beforecheckchange
125567              * Fires before a change event. Return false to cancel.
125568              * @param {Ext.menu.CheckItem} this
125569              * @param {Boolean} checked
125570              */
125571             'beforecheckchange',
125572
125573             /**
125574              * @event checkchange
125575              * Fires after a change event.
125576              * @param {Ext.menu.CheckItem} this
125577              * @param {Boolean} checked
125578              */
125579             'checkchange'
125580         );
125581
125582         me.callParent(arguments);
125583
125584         Ext.menu.Manager.registerCheckable(me);
125585
125586         if (me.group) {
125587             if (!me.iconCls) {
125588                 me.iconCls = me.groupCls;
125589             }
125590             if (me.initialConfig.hideOnClick !== false) {
125591                 me.hideOnClick = true;
125592             }
125593         }
125594     },
125595
125596     /**
125597      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
125598      * will still be accessible
125599      */
125600     disableCheckChange: function() {
125601         var me = this;
125602
125603         if (me.iconEl) {
125604             me.iconEl.addCls(me.disabledCls);
125605         }
125606         me.checkChangeDisabled = true;
125607     },
125608
125609     /**
125610      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
125611      */
125612     enableCheckChange: function() {
125613         var me = this;
125614
125615         me.iconEl.removeCls(me.disabledCls);
125616         me.checkChangeDisabled = false;
125617     },
125618
125619     onClick: function(e) {
125620         var me = this;
125621         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
125622             me.setChecked(!me.checked);
125623         }
125624         this.callParent([e]);
125625     },
125626
125627     onDestroy: function() {
125628         Ext.menu.Manager.unregisterCheckable(this);
125629         this.callParent(arguments);
125630     },
125631
125632     /**
125633      * Sets the checked state of the item
125634      * @param {Boolean} checked True to check, false to uncheck
125635      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
125636      */
125637     setChecked: function(checked, suppressEvents) {
125638         var me = this;
125639         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
125640             if (me.el) {
125641                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
125642             }
125643             me.checked = checked;
125644             Ext.menu.Manager.onCheckChange(me, checked);
125645             if (!suppressEvents) {
125646                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
125647                 me.fireEvent('checkchange', me, checked);
125648             }
125649         }
125650     }
125651 });
125652
125653 /**
125654  * @class Ext.menu.KeyNav
125655  * @private
125656  */
125657 Ext.define('Ext.menu.KeyNav', {
125658     extend: 'Ext.util.KeyNav',
125659
125660     requires: ['Ext.FocusManager'],
125661     
125662     constructor: function(menu) {
125663         var me = this;
125664
125665         me.menu = menu;
125666         me.callParent([menu.el, {
125667             down: me.down,
125668             enter: me.enter,
125669             esc: me.escape,
125670             left: me.left,
125671             right: me.right,
125672             space: me.enter,
125673             tab: me.tab,
125674             up: me.up
125675         }]);
125676     },
125677
125678     down: function(e) {
125679         var me = this,
125680             fi = me.menu.focusedItem;
125681
125682         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
125683             return true;
125684         }
125685         me.focusNextItem(1);
125686     },
125687
125688     enter: function(e) {
125689         var menu = this.menu,
125690             focused = menu.focusedItem;
125691  
125692         if (menu.activeItem) {
125693             menu.onClick(e);
125694         } else if (focused && focused.isFormField) {
125695             // prevent stopEvent being called
125696             return true;
125697         }
125698     },
125699
125700     escape: function(e) {
125701         Ext.menu.Manager.hideAll();
125702     },
125703
125704     focusNextItem: function(step) {
125705         var menu = this.menu,
125706             items = menu.items,
125707             focusedItem = menu.focusedItem,
125708             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
125709             idx = startIdx + step;
125710
125711         while (idx != startIdx) {
125712             if (idx < 0) {
125713                 idx = items.length - 1;
125714             } else if (idx >= items.length) {
125715                 idx = 0;
125716             }
125717
125718             var item = items.getAt(idx);
125719             if (menu.canActivateItem(item)) {
125720                 menu.setActiveItem(item);
125721                 break;
125722             }
125723             idx += step;
125724         }
125725     },
125726
125727     isWhitelisted: function(item) {
125728         return Ext.FocusManager.isWhitelisted(item);
125729     },
125730
125731     left: function(e) {
125732         var menu = this.menu,
125733             fi = menu.focusedItem,
125734             ai = menu.activeItem;
125735
125736         if (fi && this.isWhitelisted(fi)) {
125737             return true;
125738         }
125739
125740         menu.hide();
125741         if (menu.parentMenu) {
125742             menu.parentMenu.focus();
125743         }
125744     },
125745
125746     right: function(e) {
125747         var menu = this.menu,
125748             fi = menu.focusedItem,
125749             ai = menu.activeItem,
125750             am;
125751
125752         if (fi && this.isWhitelisted(fi)) {
125753             return true;
125754         }
125755
125756         if (ai) {
125757             am = menu.activeItem.menu;
125758             if (am) {
125759                 ai.expandMenu(0);
125760                 Ext.defer(function() {
125761                     am.setActiveItem(am.items.getAt(0));
125762                 }, 25);
125763             }
125764         }
125765     },
125766
125767     tab: function(e) {
125768         var me = this;
125769
125770         if (e.shiftKey) {
125771             me.up(e);
125772         } else {
125773             me.down(e);
125774         }
125775     },
125776
125777     up: function(e) {
125778         var me = this,
125779             fi = me.menu.focusedItem;
125780
125781         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
125782             return true;
125783         }
125784         me.focusNextItem(-1);
125785     }
125786 });
125787 /**
125788  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
125789  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
125790  *
125791  *     @example
125792  *     Ext.create('Ext.menu.Menu', {
125793  *         width: 100,
125794  *         height: 100,
125795  *         floating: false,  // usually you want this set to True (default)
125796  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125797  *         items: [{
125798  *             text: 'icon item',
125799  *             iconCls: 'add16'
125800  *         },{
125801  *             xtype: 'menuseparator'
125802  *         },{
125803  *            text: 'seperator above',
125804  *         },{
125805  *            text: 'regular item',
125806  *         }]
125807  *     });
125808  */
125809 Ext.define('Ext.menu.Separator', {
125810     extend: 'Ext.menu.Item',
125811     alias: 'widget.menuseparator',
125812
125813     /**
125814      * @cfg {String} activeCls @hide
125815      */
125816
125817     /**
125818      * @cfg {Boolean} canActivate @hide
125819      */
125820     canActivate: false,
125821
125822     /**
125823      * @cfg {Boolean} clickHideDelay @hide
125824      */
125825
125826     /**
125827      * @cfg {Boolean} destroyMenu @hide
125828      */
125829
125830     /**
125831      * @cfg {Boolean} disabledCls @hide
125832      */
125833
125834     focusable: false,
125835
125836     /**
125837      * @cfg {String} href @hide
125838      */
125839
125840     /**
125841      * @cfg {String} hrefTarget @hide
125842      */
125843
125844     /**
125845      * @cfg {Boolean} hideOnClick @hide
125846      */
125847     hideOnClick: false,
125848
125849     /**
125850      * @cfg {String} icon @hide
125851      */
125852
125853     /**
125854      * @cfg {String} iconCls @hide
125855      */
125856
125857     /**
125858      * @cfg {Object} menu @hide
125859      */
125860
125861     /**
125862      * @cfg {String} menuAlign @hide
125863      */
125864
125865     /**
125866      * @cfg {Number} menuExpandDelay @hide
125867      */
125868
125869     /**
125870      * @cfg {Number} menuHideDelay @hide
125871      */
125872
125873     /**
125874      * @cfg {Boolean} plain @hide
125875      */
125876     plain: true,
125877
125878     /**
125879      * @cfg {String} separatorCls
125880      * The CSS class used by the separator item to show the incised line.
125881      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
125882      */
125883     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
125884
125885     /**
125886      * @cfg {String} text @hide
125887      */
125888     text: '&#160;',
125889
125890     onRender: function(ct, pos) {
125891         var me = this,
125892             sepCls = me.separatorCls;
125893
125894         me.cls += ' ' + sepCls;
125895
125896         me.callParent(arguments);
125897     }
125898 });
125899 /**
125900  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
125901  *
125902  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
125903  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
125904  *
125905  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
125906  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
125907  * in line with the other menu items.
125908  *
125909  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
125910  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
125911  *
125912  *     @example
125913  *     Ext.create('Ext.menu.Menu', {
125914  *         width: 100,
125915  *         height: 100,
125916  *         margin: '0 0 10 0',
125917  *         floating: false,  // usually you want this set to True (default)
125918  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125919  *         items: [{
125920  *             text: 'regular item 1'
125921  *         },{
125922  *             text: 'regular item 2'
125923  *         },{
125924  *             text: 'regular item 3'
125925  *         }]
125926  *     });
125927  *
125928  *     Ext.create('Ext.menu.Menu', {
125929  *         width: 100,
125930  *         height: 100,
125931  *         plain: true,
125932  *         floating: false,  // usually you want this set to True (default)
125933  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125934  *         items: [{
125935  *             text: 'plain item 1'
125936  *         },{
125937  *             text: 'plain item 2'
125938  *         },{
125939  *             text: 'plain item 3'
125940  *         }]
125941  *     });
125942  */
125943 Ext.define('Ext.menu.Menu', {
125944     extend: 'Ext.panel.Panel',
125945     alias: 'widget.menu',
125946     requires: [
125947         'Ext.layout.container.Fit',
125948         'Ext.layout.container.VBox',
125949         'Ext.menu.CheckItem',
125950         'Ext.menu.Item',
125951         'Ext.menu.KeyNav',
125952         'Ext.menu.Manager',
125953         'Ext.menu.Separator'
125954     ],
125955
125956     /**
125957      * @property {Ext.menu.Menu} parentMenu
125958      * The parent Menu of this Menu.
125959      */
125960
125961     /**
125962      * @cfg {Boolean} allowOtherMenus
125963      * True to allow multiple menus to be displayed at the same time.
125964      */
125965     allowOtherMenus: false,
125966
125967     /**
125968      * @cfg {String} ariaRole @hide
125969      */
125970     ariaRole: 'menu',
125971
125972     /**
125973      * @cfg {Boolean} autoRender @hide
125974      * floating is true, so autoRender always happens
125975      */
125976
125977     /**
125978      * @cfg {String} defaultAlign
125979      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
125980      * relative to its element of origin.
125981      */
125982     defaultAlign: 'tl-bl?',
125983
125984     /**
125985      * @cfg {Boolean} floating
125986      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
125987      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
125988      * used as a child item of another {@link Ext.container.Container Container}.
125989      */
125990     floating: true,
125991
125992     /**
125993      * @cfg {Boolean} @hide
125994      * Menus are constrained to the document body by default
125995      */
125996     constrain: true,
125997
125998     /**
125999      * @cfg {Boolean} [hidden=undefined]
126000      * True to initially render the Menu as hidden, requiring to be shown manually.
126001      *
126002      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
126003      */
126004     hidden: true,
126005
126006     hideMode: 'visibility',
126007
126008     /**
126009      * @cfg {Boolean} ignoreParentClicks
126010      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
126011      * so that the submenu is not dismissed when clicking the parent item.
126012      */
126013     ignoreParentClicks: false,
126014
126015     isMenu: true,
126016
126017     /**
126018      * @cfg {String/Object} layout @hide
126019      */
126020
126021     /**
126022      * @cfg {Boolean} showSeparator
126023      * True to show the icon separator.
126024      */
126025     showSeparator : true,
126026
126027     /**
126028      * @cfg {Number} minWidth
126029      * The minimum width of the Menu.
126030      */
126031     minWidth: 120,
126032
126033     /**
126034      * @cfg {Boolean} [plain=false]
126035      * True to remove the incised line down the left side of the menu and to not indent general Component items.
126036      */
126037
126038     initComponent: function() {
126039         var me = this,
126040             prefix = Ext.baseCSSPrefix,
126041             cls = [prefix + 'menu'],
126042             bodyCls = me.bodyCls ? [me.bodyCls] : [];
126043
126044         me.addEvents(
126045             /**
126046              * @event click
126047              * Fires when this menu is clicked
126048              * @param {Ext.menu.Menu} menu The menu which has been clicked
126049              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
126050              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
126051              */
126052             'click',
126053
126054             /**
126055              * @event mouseenter
126056              * Fires when the mouse enters this menu
126057              * @param {Ext.menu.Menu} menu The menu
126058              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126059              */
126060             'mouseenter',
126061
126062             /**
126063              * @event mouseleave
126064              * Fires when the mouse leaves this menu
126065              * @param {Ext.menu.Menu} menu The menu
126066              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126067              */
126068             'mouseleave',
126069
126070             /**
126071              * @event mouseover
126072              * Fires when the mouse is hovering over this menu
126073              * @param {Ext.menu.Menu} menu The menu
126074              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
126075              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126076              */
126077             'mouseover'
126078         );
126079
126080         Ext.menu.Manager.register(me);
126081
126082         // Menu classes
126083         if (me.plain) {
126084             cls.push(prefix + 'menu-plain');
126085         }
126086         me.cls = cls.join(' ');
126087
126088         // Menu body classes
126089         bodyCls.unshift(prefix + 'menu-body');
126090         me.bodyCls = bodyCls.join(' ');
126091
126092         // Internal vbox layout, with scrolling overflow
126093         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
126094         // options if we wish to allow for such configurations on the Menu.
126095         // e.g., scrolling speed, vbox align stretch, etc.
126096         me.layout = {
126097             type: 'vbox',
126098             align: 'stretchmax',
126099             autoSize: true,
126100             clearInnerCtOnLayout: true,
126101             overflowHandler: 'Scroller'
126102         };
126103
126104         // hidden defaults to false if floating is configured as false
126105         if (me.floating === false && me.initialConfig.hidden !== true) {
126106             me.hidden = false;
126107         }
126108
126109         me.callParent(arguments);
126110
126111         me.on('beforeshow', function() {
126112             var hasItems = !!me.items.length;
126113             // FIXME: When a menu has its show cancelled because of no items, it
126114             // gets a visibility: hidden applied to it (instead of the default display: none)
126115             // Not sure why, but we remove this style when we want to show again.
126116             if (hasItems && me.rendered) {
126117                 me.el.setStyle('visibility', null);
126118             }
126119             return hasItems;
126120         });
126121     },
126122
126123     afterRender: function(ct) {
126124         var me = this,
126125             prefix = Ext.baseCSSPrefix,
126126             space = '&#160;';
126127
126128         me.callParent(arguments);
126129
126130         // TODO: Move this to a subTemplate When we support them in the future
126131         if (me.showSeparator) {
126132             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
126133                 cls: prefix + 'menu-icon-separator',
126134                 html: space
126135             });
126136         }
126137
126138         me.focusEl = me.el.createChild({
126139             cls: prefix + 'menu-focus',
126140             tabIndex: '-1',
126141             html: space
126142         });
126143
126144         me.mon(me.el, {
126145             click: me.onClick,
126146             mouseover: me.onMouseOver,
126147             scope: me
126148         });
126149         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
126150
126151         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
126152             me.iconSepEl.setHeight(me.el.getHeight());
126153         }
126154
126155         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
126156     },
126157
126158     afterLayout: function() {
126159         var me = this;
126160         me.callParent(arguments);
126161
126162         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
126163         // floating elements inherit their parent's width, making them the width of
126164         // document.body instead of the width of their contents.
126165         // This includes left/right dock items.
126166         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
126167             var innerCt = me.layout.getRenderTarget(),
126168                 innerCtWidth = 0,
126169                 dis = me.dockedItems,
126170                 l = dis.length,
126171                 i = 0,
126172                 di, clone, newWidth;
126173
126174             innerCtWidth = innerCt.getWidth();
126175
126176             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
126177
126178             // First set the body to the new width
126179             me.body.setWidth(newWidth);
126180
126181             // Now we calculate additional width (docked items) and set the el's width
126182             for (; i < l, di = dis.getAt(i); i++) {
126183                 if (di.dock == 'left' || di.dock == 'right') {
126184                     newWidth += di.getWidth();
126185                 }
126186             }
126187             me.el.setWidth(newWidth);
126188         }
126189     },
126190     
126191     getBubbleTarget: function(){
126192         return this.parentMenu || this.callParent();
126193     },
126194
126195     /**
126196      * Returns whether a menu item can be activated or not.
126197      * @return {Boolean}
126198      */
126199     canActivateItem: function(item) {
126200         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
126201     },
126202
126203     /**
126204      * Deactivates the current active item on the menu, if one exists.
126205      */
126206     deactivateActiveItem: function() {
126207         var me = this;
126208
126209         if (me.activeItem) {
126210             me.activeItem.deactivate();
126211             if (!me.activeItem.activated) {
126212                 delete me.activeItem;
126213             }
126214         }
126215
126216         // only blur if focusedItem is not a filter
126217         if (me.focusedItem && !me.filtered) {
126218             me.focusedItem.blur();
126219             if (!me.focusedItem.$focused) {
126220                 delete me.focusedItem;
126221             }
126222         }
126223     },
126224
126225     clearStretch: function () {
126226         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
126227         // reconsider them unless we clear the dimensions on the el's here:
126228         if (this.rendered) {
126229             this.items.each(function (item) {
126230                 // each menuItem component needs to layout again, so clear its cache
126231                 if (item.componentLayout) {
126232                     delete item.componentLayout.lastComponentSize;
126233                 }
126234                 if (item.el) {
126235                     item.el.setWidth(null);
126236                 }
126237             });
126238         }
126239     },
126240
126241     onAdd: function () {
126242         var me = this;
126243
126244         me.clearStretch();
126245         me.callParent(arguments);
126246
126247         if (Ext.isIE6 || Ext.isIE7) {
126248             // TODO - why does this need to be done (and not ok to do now)?
126249             Ext.Function.defer(me.doComponentLayout, 10, me);
126250         }
126251     },
126252
126253     onRemove: function () {
126254         this.clearStretch();
126255         this.callParent(arguments);
126256
126257     },
126258
126259     redoComponentLayout: function () {
126260         if (this.rendered) {
126261             this.clearStretch();
126262             this.doComponentLayout();
126263         }
126264     },
126265
126266     // inherit docs
126267     getFocusEl: function() {
126268         return this.focusEl;
126269     },
126270
126271     // inherit docs
126272     hide: function() {
126273         this.deactivateActiveItem();
126274         this.callParent(arguments);
126275     },
126276
126277     // private
126278     getItemFromEvent: function(e) {
126279         return this.getChildByElement(e.getTarget());
126280     },
126281
126282     lookupComponent: function(cmp) {
126283         var me = this;
126284
126285         if (Ext.isString(cmp)) {
126286             cmp = me.lookupItemFromString(cmp);
126287         } else if (Ext.isObject(cmp)) {
126288             cmp = me.lookupItemFromObject(cmp);
126289         }
126290
126291         // Apply our minWidth to all of our child components so it's accounted
126292         // for in our VBox layout
126293         cmp.minWidth = cmp.minWidth || me.minWidth;
126294
126295         return cmp;
126296     },
126297
126298     // private
126299     lookupItemFromObject: function(cmp) {
126300         var me = this,
126301             prefix = Ext.baseCSSPrefix,
126302             cls,
126303             intercept;
126304
126305         if (!cmp.isComponent) {
126306             if (!cmp.xtype) {
126307                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
126308             } else {
126309                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
126310             }
126311         }
126312
126313         if (cmp.isMenuItem) {
126314             cmp.parentMenu = me;
126315         }
126316
126317         if (!cmp.isMenuItem && !cmp.dock) {
126318             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
126319             intercept = Ext.Function.createInterceptor;
126320
126321             // Wrap focus/blur to control component focus
126322             cmp.focus = intercept(cmp.focus, function() {
126323                 this.$focused = true;
126324             }, cmp);
126325             cmp.blur = intercept(cmp.blur, function() {
126326                 this.$focused = false;
126327             }, cmp);
126328
126329             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
126330                 cls.push(prefix + 'menu-item-indent');
126331             }
126332
126333             if (cmp.rendered) {
126334                 cmp.el.addCls(cls);
126335             } else {
126336                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
126337             }
126338             cmp.isMenuItem = true;
126339         }
126340         return cmp;
126341     },
126342
126343     // private
126344     lookupItemFromString: function(cmp) {
126345         return (cmp == 'separator' || cmp == '-') ?
126346             Ext.createWidget('menuseparator')
126347             : Ext.createWidget('menuitem', {
126348                 canActivate: false,
126349                 hideOnClick: false,
126350                 plain: true,
126351                 text: cmp
126352             });
126353     },
126354
126355     onClick: function(e) {
126356         var me = this,
126357             item;
126358
126359         if (me.disabled) {
126360             e.stopEvent();
126361             return;
126362         }
126363
126364         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
126365             item = me.getItemFromEvent(e) || me.activeItem;
126366
126367             if (item) {
126368                 if (item.getXTypes().indexOf('menuitem') >= 0) {
126369                     if (!item.menu || !me.ignoreParentClicks) {
126370                         item.onClick(e);
126371                     } else {
126372                         e.stopEvent();
126373                     }
126374                 }
126375             }
126376             me.fireEvent('click', me, item, e);
126377         }
126378     },
126379
126380     onDestroy: function() {
126381         var me = this;
126382
126383         Ext.menu.Manager.unregister(me);
126384         if (me.rendered) {
126385             me.el.un(me.mouseMonitor);
126386             me.keyNav.destroy();
126387             delete me.keyNav;
126388         }
126389         me.callParent(arguments);
126390     },
126391
126392     onMouseLeave: function(e) {
126393         var me = this;
126394
126395         me.deactivateActiveItem();
126396
126397         if (me.disabled) {
126398             return;
126399         }
126400
126401         me.fireEvent('mouseleave', me, e);
126402     },
126403
126404     onMouseOver: function(e) {
126405         var me = this,
126406             fromEl = e.getRelatedTarget(),
126407             mouseEnter = !me.el.contains(fromEl),
126408             item = me.getItemFromEvent(e);
126409
126410         if (mouseEnter && me.parentMenu) {
126411             me.parentMenu.setActiveItem(me.parentItem);
126412             me.parentMenu.mouseMonitor.mouseenter();
126413         }
126414
126415         if (me.disabled) {
126416             return;
126417         }
126418
126419         if (item) {
126420             me.setActiveItem(item);
126421             if (item.activated && item.expandMenu) {
126422                 item.expandMenu();
126423             }
126424         }
126425         if (mouseEnter) {
126426             me.fireEvent('mouseenter', me, e);
126427         }
126428         me.fireEvent('mouseover', me, item, e);
126429     },
126430
126431     setActiveItem: function(item) {
126432         var me = this;
126433
126434         if (item && (item != me.activeItem && item != me.focusedItem)) {
126435             me.deactivateActiveItem();
126436             if (me.canActivateItem(item)) {
126437                 if (item.activate) {
126438                     item.activate();
126439                     if (item.activated) {
126440                         me.activeItem = item;
126441                         me.focusedItem = item;
126442                         me.focus();
126443                     }
126444                 } else {
126445                     item.focus();
126446                     me.focusedItem = item;
126447                 }
126448             }
126449             item.el.scrollIntoView(me.layout.getRenderTarget());
126450         }
126451     },
126452
126453     /**
126454      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
126455      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
126456      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
126457      * Defaults to `{@link #defaultAlign}`.
126458      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
126459      * @return {Ext.menu.Menu} This Menu.
126460      */
126461     showBy: function(cmp, pos, off) {
126462         var me = this,
126463             xy,
126464             region;
126465
126466         if (me.floating && cmp) {
126467             me.layout.autoSize = true;
126468
126469             // show off-screen first so that we can calc position without causing a visual jump
126470             me.doAutoRender();
126471             delete me.needsLayout;
126472
126473             // Component or Element
126474             cmp = cmp.el || cmp;
126475
126476             // Convert absolute to floatParent-relative coordinates if necessary.
126477             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
126478             if (me.floatParent) {
126479                 region = me.floatParent.getTargetEl().getViewRegion();
126480                 xy[0] -= region.x;
126481                 xy[1] -= region.y;
126482             }
126483             me.showAt(xy);
126484         }
126485         return me;
126486     },
126487
126488     doConstrain : function() {
126489         var me = this,
126490             y = me.el.getY(),
126491             max, full,
126492             vector,
126493             returnY = y, normalY, parentEl, scrollTop, viewHeight;
126494
126495         delete me.height;
126496         me.setSize();
126497         full = me.getHeight();
126498         if (me.floating) {
126499             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
126500             parentEl = Ext.fly(me.el.getScopeParent());
126501             scrollTop = parentEl.getScroll().top;
126502             viewHeight = parentEl.getViewSize().height;
126503             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
126504             //of the view.
126505             normalY = y - scrollTop;
126506             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
126507             if (full > viewHeight) {
126508                 max = viewHeight;
126509                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
126510                 returnY = y - normalY;
126511             } else if (max < full) {
126512                 returnY = y - (full - max);
126513                 max = full;
126514             }
126515         }else{
126516             max = me.getHeight();
126517         }
126518         // Always respect maxHeight
126519         if (me.maxHeight){
126520             max = Math.min(me.maxHeight, max);
126521         }
126522         if (full > max && max > 0){
126523             me.layout.autoSize = false;
126524             me.setHeight(max);
126525             if (me.showSeparator){
126526                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
126527             }
126528         }
126529         vector = me.getConstrainVector(me.el.getScopeParent());
126530         if (vector) {
126531             me.setPosition(me.getPosition()[0] + vector[0]);
126532         }
126533         me.el.setY(returnY);
126534     }
126535 });
126536
126537 /**
126538  * A menu containing a Ext.picker.Color Component.
126539  *
126540  * Notes:
126541  *
126542  *   - Although not listed here, the **constructor** for this class accepts all of the
126543  *     configuration options of {@link Ext.picker.Color}.
126544  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
126545  *     applied to the **initialConfig** property of the ColorMenu. Applying
126546  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
126547  *     affect the ColorPicker's configuration.
126548  *
126549  * Example:
126550  *
126551  *     @example
126552  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
126553  *         value: '000000'
126554  *     });
126555  *
126556  *     Ext.create('Ext.menu.Menu', {
126557  *         width: 100,
126558  *         height: 90,
126559  *         floating: false,  // usually you want this set to True (default)
126560  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126561  *         items: [{
126562  *             text: 'choose a color',
126563  *             menu: colorPicker
126564  *         },{
126565  *             iconCls: 'add16',
126566  *             text: 'icon item'
126567  *         },{
126568  *             text: 'regular item'
126569  *         }]
126570  *     });
126571  */
126572  Ext.define('Ext.menu.ColorPicker', {
126573      extend: 'Ext.menu.Menu',
126574
126575      alias: 'widget.colormenu',
126576
126577      requires: [
126578         'Ext.picker.Color'
126579      ],
126580
126581     /**
126582      * @cfg {Boolean} hideOnClick
126583      * False to continue showing the menu after a date is selected.
126584      */
126585     hideOnClick : true,
126586
126587     /**
126588      * @cfg {String} pickerId
126589      * An id to assign to the underlying color picker.
126590      */
126591     pickerId : null,
126592
126593     /**
126594      * @cfg {Number} maxHeight
126595      * @hide
126596      */
126597
126598     /**
126599      * @property {Ext.picker.Color} picker
126600      * The {@link Ext.picker.Color} instance for this ColorMenu
126601      */
126602
126603     /**
126604      * @event click
126605      * @hide
126606      */
126607
126608     /**
126609      * @event itemclick
126610      * @hide
126611      */
126612
126613     initComponent : function(){
126614         var me = this,
126615             cfg = Ext.apply({}, me.initialConfig);
126616
126617         // Ensure we don't get duplicate listeners
126618         delete cfg.listeners;
126619         Ext.apply(me, {
126620             plain: true,
126621             showSeparator: false,
126622             items: Ext.applyIf({
126623                 cls: Ext.baseCSSPrefix + 'menu-color-item',
126624                 id: me.pickerId,
126625                 xtype: 'colorpicker'
126626             }, cfg)
126627         });
126628
126629         me.callParent(arguments);
126630
126631         me.picker = me.down('colorpicker');
126632
126633         /**
126634          * @event select
126635          * @alias Ext.picker.Color#select
126636          */
126637         me.relayEvents(me.picker, ['select']);
126638
126639         if (me.hideOnClick) {
126640             me.on('select', me.hidePickerOnSelect, me);
126641         }
126642     },
126643
126644     /**
126645      * Hides picker on select if hideOnClick is true
126646      * @private
126647      */
126648     hidePickerOnSelect: function() {
126649         Ext.menu.Manager.hideAll();
126650     }
126651  });
126652 /**
126653  * A menu containing an Ext.picker.Date Component.
126654  *
126655  * Notes:
126656  *
126657  * - Although not listed here, the **constructor** for this class accepts all of the
126658  *   configuration options of **{@link Ext.picker.Date}**.
126659  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
126660  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
126661  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
126662  *
126663  * Example:
126664  *
126665  *     @example
126666  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
126667  *         handler: function(dp, date){
126668  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
126669  *         }
126670  *     });
126671  *
126672  *     Ext.create('Ext.menu.Menu', {
126673  *         width: 100,
126674  *         height: 90,
126675  *         floating: false,  // usually you want this set to True (default)
126676  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126677  *         items: [{
126678  *             text: 'choose a date',
126679  *             menu: dateMenu
126680  *         },{
126681  *             iconCls: 'add16',
126682  *             text: 'icon item'
126683  *         },{
126684  *             text: 'regular item'
126685  *         }]
126686  *     });
126687  */
126688  Ext.define('Ext.menu.DatePicker', {
126689      extend: 'Ext.menu.Menu',
126690
126691      alias: 'widget.datemenu',
126692
126693      requires: [
126694         'Ext.picker.Date'
126695      ],
126696
126697     /**
126698      * @cfg {Boolean} hideOnClick
126699      * False to continue showing the menu after a date is selected.
126700      */
126701     hideOnClick : true,
126702
126703     /**
126704      * @cfg {String} pickerId
126705      * An id to assign to the underlying date picker.
126706      */
126707     pickerId : null,
126708
126709     /**
126710      * @cfg {Number} maxHeight
126711      * @hide
126712      */
126713
126714     /**
126715      * @property {Ext.picker.Date} picker
126716      * The {@link Ext.picker.Date} instance for this DateMenu
126717      */
126718
126719     /**
126720      * @event click
126721      * @hide
126722      */
126723
126724     /**
126725      * @event itemclick
126726      * @hide
126727      */
126728
126729     initComponent : function(){
126730         var me = this;
126731
126732         Ext.apply(me, {
126733             showSeparator: false,
126734             plain: true,
126735             border: false,
126736             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
126737             items: Ext.applyIf({
126738                 cls: Ext.baseCSSPrefix + 'menu-date-item',
126739                 id: me.pickerId,
126740                 xtype: 'datepicker'
126741             }, me.initialConfig)
126742         });
126743
126744         me.callParent(arguments);
126745
126746         me.picker = me.down('datepicker');
126747         /**
126748          * @event select
126749          * @alias Ext.picker.Date#select
126750          */
126751         me.relayEvents(me.picker, ['select']);
126752
126753         if (me.hideOnClick) {
126754             me.on('select', me.hidePickerOnSelect, me);
126755         }
126756     },
126757
126758     hidePickerOnSelect: function() {
126759         Ext.menu.Manager.hideAll();
126760     }
126761  });
126762 /**
126763  * This class is used to display small visual icons in the header of a panel. There are a set of
126764  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
126765  * can be used to provide a function that will respond to any click events. In general, this class
126766  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
126767  * configuration on the Panel itself.
126768  *
126769  *     @example
126770  *     Ext.create('Ext.panel.Panel', {
126771  *         width: 200,
126772  *         height: 200,
126773  *         renderTo: document.body,
126774  *         title: 'A Panel',
126775  *         tools: [{
126776  *             type: 'help',
126777  *             handler: function(){
126778  *                 // show help here
126779  *             }
126780  *         }, {
126781  *             itemId: 'refresh',
126782  *             type: 'refresh',
126783  *             hidden: true,
126784  *             handler: function(){
126785  *                 // do refresh
126786  *             }
126787  *         }, {
126788  *             type: 'search',
126789  *             handler: function(event, target, owner, tool){
126790  *                 // do search
126791  *                 owner.child('#refresh').show();
126792  *             }
126793  *         }]
126794  *     });
126795  */
126796 Ext.define('Ext.panel.Tool', {
126797     extend: 'Ext.Component',
126798     requires: ['Ext.tip.QuickTipManager'],
126799     alias: 'widget.tool',
126800
126801     baseCls: Ext.baseCSSPrefix + 'tool',
126802     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
126803     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
126804     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
126805     ariaRole: 'button',
126806     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
126807
126808     /**
126809      * @cfg {Function} handler
126810      * A function to execute when the tool is clicked. Arguments passed are:
126811      *
126812      * - **event** : Ext.EventObject - The click event.
126813      * - **toolEl** : Ext.Element - The tool Element.
126814      * - **owner** : Ext.panel.Header - The host panel header.
126815      * - **tool** : Ext.panel.Tool - The tool object
126816      */
126817
126818     /**
126819      * @cfg {Object} scope
126820      * The scope to execute the {@link #handler} function. Defaults to the tool.
126821      */
126822
126823     /**
126824      * @cfg {String} type
126825      * The type of tool to render. The following types are available:
126826      *
126827      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
126828      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
126829      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
126830      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
126831      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
126832      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
126833      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
126834      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
126835      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
126836      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
126837      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
126838      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
126839      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
126840      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
126841      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
126842      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
126843      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
126844      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
126845      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
126846      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
126847      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
126848      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
126849      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
126850      */
126851
126852     /**
126853      * @cfg {String/Object} tooltip
126854      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
126855      * object
126856      */
126857
126858      /**
126859      * @cfg {String} tooltipType
126860      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
126861      */
126862     tooltipType: 'qtip',
126863
126864     /**
126865      * @cfg {Boolean} stopEvent
126866      * Specify as false to allow click event to propagate.
126867      */
126868     stopEvent: true,
126869
126870     initComponent: function() {
126871         var me = this;
126872         me.addEvents(
126873             /**
126874              * @event click
126875              * Fires when the tool is clicked
126876              * @param {Ext.panel.Tool} this
126877              * @param {Ext.EventObject} e The event object
126878              */
126879             'click'
126880         );
126881
126882
126883         me.type = me.type || me.id;
126884
126885         Ext.applyIf(me.renderData, {
126886             baseCls: me.baseCls,
126887             blank: Ext.BLANK_IMAGE_URL,
126888             type: me.type
126889         });
126890
126891         me.addChildEls('toolEl');
126892
126893         // alias qtip, should use tooltip since it's what we have in the docs
126894         me.tooltip = me.tooltip || me.qtip;
126895         me.callParent();
126896     },
126897
126898     // inherit docs
126899     afterRender: function() {
126900         var me = this,
126901             attr;
126902
126903         me.callParent(arguments);
126904         if (me.tooltip) {
126905             if (Ext.isObject(me.tooltip)) {
126906                 Ext.tip.QuickTipManager.register(Ext.apply({
126907                     target: me.id
126908                 }, me.tooltip));
126909             }
126910             else {
126911                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
126912                 me.toolEl.dom.setAttribute(attr, me.tooltip);
126913             }
126914         }
126915
126916         me.mon(me.toolEl, {
126917             click: me.onClick,
126918             mousedown: me.onMouseDown,
126919             mouseover: me.onMouseOver,
126920             mouseout: me.onMouseOut,
126921             scope: me
126922         });
126923     },
126924
126925     /**
126926      * Sets the type of the tool. Allows the icon to be changed.
126927      * @param {String} type The new type. See the {@link #type} config.
126928      * @return {Ext.panel.Tool} this
126929      */
126930     setType: function(type) {
126931         var me = this;
126932
126933         me.type = type;
126934         if (me.rendered) {
126935             me.toolEl.dom.className = me.baseCls + '-' + type;
126936         }
126937         return me;
126938     },
126939
126940     /**
126941      * Binds this tool to a component.
126942      * @private
126943      * @param {Ext.Component} component The component
126944      */
126945     bindTo: function(component) {
126946         this.owner = component;
126947     },
126948
126949     /**
126950      * Called when the tool element is clicked
126951      * @private
126952      * @param {Ext.EventObject} e
126953      * @param {HTMLElement} target The target element
126954      */
126955     onClick: function(e, target) {
126956         var me = this,
126957             owner;
126958
126959         if (me.disabled) {
126960             return false;
126961         }
126962         owner = me.owner || me.ownerCt;
126963
126964         //remove the pressed + over class
126965         me.el.removeCls(me.toolPressedCls);
126966         me.el.removeCls(me.toolOverCls);
126967
126968         if (me.stopEvent !== false) {
126969             e.stopEvent();
126970         }
126971
126972         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
126973         me.fireEvent('click', me, e);
126974         return true;
126975     },
126976
126977     // inherit docs
126978     onDestroy: function(){
126979         if (Ext.isObject(this.tooltip)) {
126980             Ext.tip.QuickTipManager.unregister(this.id);
126981         }
126982         this.callParent();
126983     },
126984
126985     /**
126986      * Called when the user presses their mouse button down on a tool
126987      * Adds the press class ({@link #toolPressedCls})
126988      * @private
126989      */
126990     onMouseDown: function() {
126991         if (this.disabled) {
126992             return false;
126993         }
126994
126995         this.el.addCls(this.toolPressedCls);
126996     },
126997
126998     /**
126999      * Called when the user rolls over a tool
127000      * Adds the over class ({@link #toolOverCls})
127001      * @private
127002      */
127003     onMouseOver: function() {
127004         if (this.disabled) {
127005             return false;
127006         }
127007         this.el.addCls(this.toolOverCls);
127008     },
127009
127010     /**
127011      * Called when the user rolls out from a tool.
127012      * Removes the over class ({@link #toolOverCls})
127013      * @private
127014      */
127015     onMouseOut: function() {
127016         this.el.removeCls(this.toolOverCls);
127017     }
127018 });
127019 /**
127020  * @class Ext.resizer.Handle
127021  * @extends Ext.Component
127022  *
127023  * Provides a handle for 9-point resizing of Elements or Components.
127024  */
127025 Ext.define('Ext.resizer.Handle', {
127026     extend: 'Ext.Component',
127027     handleCls: '',
127028     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
127029     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
127030     // which will be passed in as a region configuration.
127031     region: '',
127032
127033     onRender: function() {
127034         this.addCls(
127035             this.baseHandleCls,
127036             this.baseHandleCls + '-' + this.region,
127037             this.handleCls
127038         );
127039         this.callParent(arguments);
127040         this.el.unselectable();
127041     }
127042 });
127043
127044 /**
127045  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
127046  * (or component's element) and positioned absolute.
127047  *
127048  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
127049  * The original element can be accessed through the originalTarget property.
127050  *
127051  * Here is the list of valid resize handles:
127052  *
127053  *     Value   Description
127054  *     ------  -------------------
127055  *      'n'     north
127056  *      's'     south
127057  *      'e'     east
127058  *      'w'     west
127059  *      'nw'    northwest
127060  *      'sw'    southwest
127061  *      'se'    southeast
127062  *      'ne'    northeast
127063  *      'all'   all
127064  *
127065  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
127066  *
127067  * Here's an example showing the creation of a typical Resizer:
127068  *
127069  *     Ext.create('Ext.resizer.Resizer', {
127070  *         el: 'elToResize',
127071  *         handles: 'all',
127072  *         minWidth: 200,
127073  *         minHeight: 100,
127074  *         maxWidth: 500,
127075  *         maxHeight: 400,
127076  *         pinned: true
127077  *     });
127078  */
127079 Ext.define('Ext.resizer.Resizer', {
127080     mixins: {
127081         observable: 'Ext.util.Observable'
127082     },
127083     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
127084
127085     alternateClassName: 'Ext.Resizable',
127086
127087     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
127088     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
127089     overCls:   Ext.baseCSSPrefix + 'resizable-over',
127090     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
127091
127092     /**
127093      * @cfg {Boolean} dynamic
127094      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
127095      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
127096      * configured as {@link Ext.Component#resizable}.
127097      *
127098      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
127099      * updated on mouseup.
127100      */
127101     dynamic: true,
127102
127103     /**
127104      * @cfg {String} handles
127105      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
127106      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
127107      * of `'n s e w ne nw se sw'`.
127108      */
127109     handles: 's e se',
127110
127111     /**
127112      * @cfg {Number} height
127113      * Optional. The height to set target to in pixels
127114      */
127115     height : null,
127116
127117     /**
127118      * @cfg {Number} width
127119      * Optional. The width to set the target to in pixels
127120      */
127121     width : null,
127122
127123     /**
127124      * @cfg {Number} heightIncrement
127125      * The increment to snap the height resize in pixels.
127126      */
127127     heightIncrement : 0,
127128
127129     /**
127130      * @cfg {Number} widthIncrement
127131      * The increment to snap the width resize in pixels.
127132      */
127133     widthIncrement : 0,
127134
127135     /**
127136      * @cfg {Number} minHeight
127137      * The minimum height for the element
127138      */
127139     minHeight : 20,
127140
127141     /**
127142      * @cfg {Number} minWidth
127143      * The minimum width for the element
127144      */
127145     minWidth : 20,
127146
127147     /**
127148      * @cfg {Number} maxHeight
127149      * The maximum height for the element
127150      */
127151     maxHeight : 10000,
127152
127153     /**
127154      * @cfg {Number} maxWidth
127155      * The maximum width for the element
127156      */
127157     maxWidth : 10000,
127158
127159     /**
127160      * @cfg {Boolean} pinned
127161      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
127162      */
127163     pinned: false,
127164
127165     /**
127166      * @cfg {Boolean} preserveRatio
127167      * True to preserve the original ratio between height and width during resize
127168      */
127169     preserveRatio: false,
127170
127171     /**
127172      * @cfg {Boolean} transparent
127173      * True for transparent handles. This is only applied at config time.
127174      */
127175     transparent: false,
127176
127177     /**
127178      * @cfg {Ext.Element/Ext.util.Region} constrainTo
127179      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
127180      */
127181
127182     possiblePositions: {
127183         n:  'north',
127184         s:  'south',
127185         e:  'east',
127186         w:  'west',
127187         se: 'southeast',
127188         sw: 'southwest',
127189         nw: 'northwest',
127190         ne: 'northeast'
127191     },
127192
127193     /**
127194      * @cfg {Ext.Element/Ext.Component} target
127195      * The Element or Component to resize.
127196      */
127197
127198     /**
127199      * @property {Ext.Element} el
127200      * Outer element for resizing behavior.
127201      */
127202
127203     constructor: function(config) {
127204         var me = this,
127205             target,
127206             tag,
127207             handles = me.handles,
127208             handleCls,
127209             possibles,
127210             len,
127211             i = 0,
127212             pos;
127213
127214         this.addEvents(
127215             /**
127216              * @event beforeresize
127217              * Fired before resize is allowed. Return false to cancel resize.
127218              * @param {Ext.resizer.Resizer} this
127219              * @param {Number} width The start width
127220              * @param {Number} height The start height
127221              * @param {Ext.EventObject} e The mousedown event
127222              */
127223             'beforeresize',
127224             /**
127225              * @event resizedrag
127226              * Fires during resizing. Return false to cancel resize.
127227              * @param {Ext.resizer.Resizer} this
127228              * @param {Number} width The new width
127229              * @param {Number} height The new height
127230              * @param {Ext.EventObject} e The mousedown event
127231              */
127232             'resizedrag',
127233             /**
127234              * @event resize
127235              * Fired after a resize.
127236              * @param {Ext.resizer.Resizer} this
127237              * @param {Number} width The new width
127238              * @param {Number} height The new height
127239              * @param {Ext.EventObject} e The mouseup event
127240              */
127241             'resize'
127242         );
127243
127244         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
127245             target = config;
127246             config = arguments[1] || {};
127247             config.target = target;
127248         }
127249         // will apply config to this
127250         me.mixins.observable.constructor.call(me, config);
127251
127252         // If target is a Component, ensure that we pull the element out.
127253         // Resizer must examine the underlying Element.
127254         target = me.target;
127255         if (target) {
127256             if (target.isComponent) {
127257                 me.el = target.getEl();
127258                 if (target.minWidth) {
127259                     me.minWidth = target.minWidth;
127260                 }
127261                 if (target.minHeight) {
127262                     me.minHeight = target.minHeight;
127263                 }
127264                 if (target.maxWidth) {
127265                     me.maxWidth = target.maxWidth;
127266                 }
127267                 if (target.maxHeight) {
127268                     me.maxHeight = target.maxHeight;
127269                 }
127270                 if (target.floating) {
127271                     if (!this.hasOwnProperty('handles')) {
127272                         this.handles = 'n ne e se s sw w nw';
127273                     }
127274                 }
127275             } else {
127276                 me.el = me.target = Ext.get(target);
127277             }
127278         }
127279         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
127280         else {
127281             me.target = me.el = Ext.get(me.el);
127282         }
127283
127284         // Tags like textarea and img cannot
127285         // have children and therefore must
127286         // be wrapped
127287         tag = me.el.dom.tagName;
127288         if (tag == 'TEXTAREA' || tag == 'IMG') {
127289             /**
127290              * @property {Ext.Element/Ext.Component} originalTarget
127291              * Reference to the original resize target if the element of the original resize target was an IMG or a
127292              * TEXTAREA which must be wrapped in a DIV.
127293              */
127294             me.originalTarget = me.target;
127295             me.target = me.el = me.el.wrap({
127296                 cls: me.wrapCls,
127297                 id: me.el.id + '-rzwrap'
127298             });
127299
127300             // Transfer originalTarget's positioning/sizing
127301             me.el.setPositioning(me.originalTarget.getPositioning());
127302             me.originalTarget.clearPositioning();
127303             var box = me.originalTarget.getBox();
127304             me.el.setBox(box);
127305         }
127306
127307         // Position the element, this enables us to absolute position
127308         // the handles within this.el
127309         me.el.position();
127310         if (me.pinned) {
127311             me.el.addCls(me.pinnedCls);
127312         }
127313
127314         /**
127315          * @property {Ext.resizer.ResizeTracker} resizeTracker
127316          */
127317         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
127318             disabled: me.disabled,
127319             target: me.target,
127320             constrainTo: me.constrainTo,
127321             overCls: me.overCls,
127322             throttle: me.throttle,
127323             originalTarget: me.originalTarget,
127324             delegate: '.' + me.handleCls,
127325             dynamic: me.dynamic,
127326             preserveRatio: me.preserveRatio,
127327             heightIncrement: me.heightIncrement,
127328             widthIncrement: me.widthIncrement,
127329             minHeight: me.minHeight,
127330             maxHeight: me.maxHeight,
127331             minWidth: me.minWidth,
127332             maxWidth: me.maxWidth
127333         });
127334
127335         // Relay the ResizeTracker's superclass events as our own resize events
127336         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
127337         me.resizeTracker.on('drag', me.onResize, me);
127338         me.resizeTracker.on('dragend', me.onResizeEnd, me);
127339
127340         if (me.handles == 'all') {
127341             me.handles = 'n s e w ne nw se sw';
127342         }
127343
127344         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
127345         possibles = me.possiblePositions;
127346         len = handles.length;
127347         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
127348
127349         for(; i < len; i++){
127350             // if specified and possible, create
127351             if (handles[i] && possibles[handles[i]]) {
127352                 pos = possibles[handles[i]];
127353                 // store a reference in this.east, this.west, etc
127354
127355                 me[pos] = Ext.create('Ext.Component', {
127356                     owner: this,
127357                     region: pos,
127358                     cls: handleCls + pos,
127359                     renderTo: me.el
127360                 });
127361                 me[pos].el.unselectable();
127362                 if (me.transparent) {
127363                     me[pos].el.setOpacity(0);
127364                 }
127365             }
127366         }
127367
127368         // Constrain within configured maxima
127369         if (Ext.isNumber(me.width)) {
127370             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
127371         }
127372         if (Ext.isNumber(me.height)) {
127373             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
127374         }
127375
127376         // Size the element
127377         if (me.width != null || me.height != null) {
127378             if (me.originalTarget) {
127379                 me.originalTarget.setWidth(me.width);
127380                 me.originalTarget.setHeight(me.height);
127381             }
127382             me.resizeTo(me.width, me.height);
127383         }
127384
127385         me.forceHandlesHeight();
127386     },
127387
127388     disable: function() {
127389         this.resizeTracker.disable();
127390     },
127391
127392     enable: function() {
127393         this.resizeTracker.enable();
127394     },
127395
127396     /**
127397      * @private Relay the Tracker's mousedown event as beforeresize
127398      * @param tracker The Resizer
127399      * @param e The Event
127400      */
127401     onBeforeResize: function(tracker, e) {
127402         var b = this.target.getBox();
127403         return this.fireEvent('beforeresize', this, b.width, b.height, e);
127404     },
127405
127406     /**
127407      * @private Relay the Tracker's drag event as resizedrag
127408      * @param tracker The Resizer
127409      * @param e The Event
127410      */
127411     onResize: function(tracker, e) {
127412         var me = this,
127413             b = me.target.getBox();
127414         me.forceHandlesHeight();
127415         return me.fireEvent('resizedrag', me, b.width, b.height, e);
127416     },
127417
127418     /**
127419      * @private Relay the Tracker's dragend event as resize
127420      * @param tracker The Resizer
127421      * @param e The Event
127422      */
127423     onResizeEnd: function(tracker, e) {
127424         var me = this,
127425             b = me.target.getBox();
127426         me.forceHandlesHeight();
127427         return me.fireEvent('resize', me, b.width, b.height, e);
127428     },
127429
127430     /**
127431      * Perform a manual resize and fires the 'resize' event.
127432      * @param {Number} width
127433      * @param {Number} height
127434      */
127435     resizeTo : function(width, height){
127436         this.target.setSize(width, height);
127437         this.fireEvent('resize', this, width, height, null);
127438     },
127439
127440     /**
127441      * Returns the element that was configured with the el or target config property. If a component was configured with
127442      * the target property then this will return the element of this component.
127443      *
127444      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127445      * nodes. The original element can be accessed through the originalTarget property.
127446      * @return {Ext.Element} element
127447      */
127448     getEl : function() {
127449         return this.el;
127450     },
127451
127452     /**
127453      * Returns the element or component that was configured with the target config property.
127454      *
127455      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127456      * nodes. The original element can be accessed through the originalTarget property.
127457      * @return {Ext.Element/Ext.Component}
127458      */
127459     getTarget: function() {
127460         return this.target;
127461     },
127462
127463     destroy: function() {
127464         var h;
127465         for (var i = 0, l = this.handles.length; i < l; i++) {
127466             h = this[this.possiblePositions[this.handles[i]]];
127467             delete h.owner;
127468             Ext.destroy(h);
127469         }
127470     },
127471
127472     /**
127473      * @private
127474      * Fix IE6 handle height issue.
127475      */
127476     forceHandlesHeight : function() {
127477         var me = this,
127478             handle;
127479         if (Ext.isIE6) {
127480             handle = me.east;
127481             if (handle) {
127482                 handle.setHeight(me.el.getHeight());
127483             }
127484             handle = me.west;
127485             if (handle) {
127486                 handle.setHeight(me.el.getHeight());
127487             }
127488             me.el.repaint();
127489         }
127490     }
127491 });
127492
127493 /**
127494  * @class Ext.resizer.ResizeTracker
127495  * @extends Ext.dd.DragTracker
127496  * Private utility class for Ext.resizer.Resizer.
127497  * @private
127498  */
127499 Ext.define('Ext.resizer.ResizeTracker', {
127500     extend: 'Ext.dd.DragTracker',
127501     dynamic: true,
127502     preserveRatio: false,
127503
127504     // Default to no constraint
127505     constrainTo: null,
127506     
127507     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
127508
127509     constructor: function(config) {
127510         var me = this;
127511
127512         if (!config.el) {
127513             if (config.target.isComponent) {
127514                 me.el = config.target.getEl();
127515             } else {
127516                 me.el = config.target;
127517             }
127518         }
127519         this.callParent(arguments);
127520
127521         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
127522         if (me.preserveRatio && me.minWidth && me.minHeight) {
127523             var widthRatio = me.minWidth / me.el.getWidth(),
127524                 heightRatio = me.minHeight / me.el.getHeight();
127525
127526             // largest ratio of minimum:size must be preserved.
127527             // So if a 400x200 pixel image has
127528             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
127529             if (heightRatio > widthRatio) {
127530                 me.minWidth = me.el.getWidth() * heightRatio;
127531             } else {
127532                 me.minHeight = me.el.getHeight() * widthRatio;
127533             }
127534         }
127535
127536         // If configured as throttled, create an instance version of resize which calls
127537         // a throttled function to perform the resize operation.
127538         if (me.throttle) {
127539             var throttledResizeFn = Ext.Function.createThrottled(function() {
127540                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127541                 }, me.throttle);
127542
127543             me.resize = function(box, direction, atEnd) {
127544                 if (atEnd) {
127545                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127546                 } else {
127547                     throttledResizeFn.apply(null, arguments);
127548                 }
127549             };
127550         }
127551     },
127552
127553     onBeforeStart: function(e) {
127554         // record the startBox
127555         this.startBox = this.el.getBox();
127556     },
127557
127558     /**
127559      * @private
127560      * Returns the object that will be resized on every mousemove event.
127561      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
127562      */
127563     getDynamicTarget: function() {
127564         var me = this,
127565             target = me.target;
127566             
127567         if (me.dynamic) {
127568             return target;
127569         } else if (!me.proxy) {
127570             me.proxy = me.createProxy(target);
127571         }
127572         me.proxy.show();
127573         return me.proxy;
127574     },
127575     
127576     /**
127577      * Create a proxy for this resizer
127578      * @param {Ext.Component/Ext.Element} target The target
127579      * @return {Ext.Element} A proxy element
127580      */
127581     createProxy: function(target){
127582         var proxy,
127583             cls = this.proxyCls,
127584             renderTo;
127585             
127586         if (target.isComponent) {
127587             proxy = target.getProxy().addCls(cls);
127588         } else {
127589             renderTo = Ext.getBody();
127590             if (Ext.scopeResetCSS) {
127591                 renderTo = Ext.getBody().createChild({
127592                     cls: Ext.baseCSSPrefix + 'reset'
127593                 });
127594             }
127595             proxy = target.createProxy({
127596                 tag: 'div',
127597                 cls: cls,
127598                 id: target.id + '-rzproxy'
127599             }, renderTo);
127600         }
127601         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
127602         return proxy;
127603     },
127604
127605     onStart: function(e) {
127606         // returns the Ext.ResizeHandle that the user started dragging
127607         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
127608
127609         // If we are using a proxy, ensure it is sized.
127610         if (!this.dynamic) {
127611             this.resize(this.startBox, {
127612                 horizontal: 'none',
127613                 vertical: 'none'
127614             });
127615         }
127616     },
127617
127618     onDrag: function(e) {
127619         // dynamic resizing, update dimensions during resize
127620         if (this.dynamic || this.proxy) {
127621             this.updateDimensions(e);
127622         }
127623     },
127624
127625     updateDimensions: function(e, atEnd) {
127626         var me = this,
127627             region = me.activeResizeHandle.region,
127628             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
127629             box = me.startBox,
127630             ratio,
127631             widthAdjust = 0,
127632             heightAdjust = 0,
127633             snappedWidth,
127634             snappedHeight,
127635             adjustX = 0,
127636             adjustY = 0,
127637             dragRatio,
127638             horizDir = offset[0] < 0 ? 'right' : 'left',
127639             vertDir = offset[1] < 0 ? 'down' : 'up',
127640             oppositeCorner,
127641             axis; // 1 = x, 2 = y, 3 = x and y.
127642
127643         switch (region) {
127644             case 'south':
127645                 heightAdjust = offset[1];
127646                 axis = 2;
127647                 break;
127648             case 'north':
127649                 heightAdjust = -offset[1];
127650                 adjustY = -heightAdjust;
127651                 axis = 2;
127652                 break;
127653             case 'east':
127654                 widthAdjust = offset[0];
127655                 axis = 1;
127656                 break;
127657             case 'west':
127658                 widthAdjust = -offset[0];
127659                 adjustX = -widthAdjust;
127660                 axis = 1;
127661                 break;
127662             case 'northeast':
127663                 heightAdjust = -offset[1];
127664                 adjustY = -heightAdjust;
127665                 widthAdjust = offset[0];
127666                 oppositeCorner = [box.x, box.y + box.height];
127667                 axis = 3;
127668                 break;
127669             case 'southeast':
127670                 heightAdjust = offset[1];
127671                 widthAdjust = offset[0];
127672                 oppositeCorner = [box.x, box.y];
127673                 axis = 3;
127674                 break;
127675             case 'southwest':
127676                 widthAdjust = -offset[0];
127677                 adjustX = -widthAdjust;
127678                 heightAdjust = offset[1];
127679                 oppositeCorner = [box.x + box.width, box.y];
127680                 axis = 3;
127681                 break;
127682             case 'northwest':
127683                 heightAdjust = -offset[1];
127684                 adjustY = -heightAdjust;
127685                 widthAdjust = -offset[0];
127686                 adjustX = -widthAdjust;
127687                 oppositeCorner = [box.x + box.width, box.y + box.height];
127688                 axis = 3;
127689                 break;
127690         }
127691
127692         var newBox = {
127693             width: box.width + widthAdjust,
127694             height: box.height + heightAdjust,
127695             x: box.x + adjustX,
127696             y: box.y + adjustY
127697         };
127698
127699         // Snap value between stops according to configured increments
127700         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
127701         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
127702         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
127703             switch (region) {
127704                 case 'northeast':
127705                     newBox.y -= snappedHeight - newBox.height;
127706                     break;
127707                 case 'north':
127708                     newBox.y -= snappedHeight - newBox.height;
127709                     break;
127710                 case 'southwest':
127711                     newBox.x -= snappedWidth - newBox.width;
127712                     break;
127713                 case 'west':
127714                     newBox.x -= snappedWidth - newBox.width;
127715                     break;
127716                 case 'northwest':
127717                     newBox.x -= snappedWidth - newBox.width;
127718                     newBox.y -= snappedHeight - newBox.height;
127719             }
127720             newBox.width = snappedWidth;
127721             newBox.height = snappedHeight;
127722         }
127723
127724         // out of bounds
127725         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
127726             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
127727
127728             // Re-adjust the X position if we were dragging the west side
127729             if (adjustX) {
127730                 newBox.x = box.x + (box.width - newBox.width);
127731             }
127732         } else {
127733             me.lastX = newBox.x;
127734         }
127735         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
127736             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
127737
127738             // Re-adjust the Y position if we were dragging the north side
127739             if (adjustY) {
127740                 newBox.y = box.y + (box.height - newBox.height);
127741             }
127742         } else {
127743             me.lastY = newBox.y;
127744         }
127745
127746         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
127747         if (me.preserveRatio || e.shiftKey) {
127748             var newHeight,
127749                 newWidth;
127750
127751             ratio = me.startBox.width / me.startBox.height;
127752
127753             // Calculate aspect ratio constrained values.
127754             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
127755             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
127756
127757             // X axis: width-only change, height must obey
127758             if (axis == 1) {
127759                 newBox.height = newHeight;
127760             }
127761
127762             // Y axis: height-only change, width must obey
127763             else if (axis == 2) {
127764                 newBox.width = newWidth;
127765             }
127766
127767             // Corner drag.
127768             else {
127769                 // Drag ratio is the ratio of the mouse point from the opposite corner.
127770                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
127771                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
127772
127773                 // If drag ratio > aspect ratio then width is dominant and height must obey
127774                 if (dragRatio > ratio) {
127775                     newBox.height = newHeight;
127776                 } else {
127777                     newBox.width = newWidth;
127778                 }
127779
127780                 // Handle dragging start coordinates
127781                 if (region == 'northeast') {
127782                     newBox.y = box.y - (newBox.height - box.height);
127783                 } else if (region == 'northwest') {
127784                     newBox.y = box.y - (newBox.height - box.height);
127785                     newBox.x = box.x - (newBox.width - box.width);
127786                 } else if (region == 'southwest') {
127787                     newBox.x = box.x - (newBox.width - box.width);
127788                 }
127789             }
127790         }
127791
127792         if (heightAdjust === 0) {
127793             vertDir = 'none';
127794         }
127795         if (widthAdjust === 0) {
127796             horizDir = 'none';
127797         }
127798         me.resize(newBox, {
127799             horizontal: horizDir,
127800             vertical: vertDir
127801         }, atEnd);
127802     },
127803
127804     getResizeTarget: function(atEnd) {
127805         return atEnd ? this.target : this.getDynamicTarget();
127806     },
127807
127808     resize: function(box, direction, atEnd) {
127809         var target = this.getResizeTarget(atEnd);
127810         if (target.isComponent) {
127811             if (target.floating) {
127812                 target.setPagePosition(box.x, box.y);
127813             }
127814             target.setSize(box.width, box.height);
127815         } else {
127816             target.setBox(box);
127817             // update the originalTarget if this was wrapped.
127818             if (this.originalTarget) {
127819                 this.originalTarget.setBox(box);
127820             }
127821         }
127822     },
127823
127824     onEnd: function(e) {
127825         this.updateDimensions(e, true);
127826         if (this.proxy) {
127827             this.proxy.hide();
127828         }
127829     }
127830 });
127831
127832 /**
127833  * @class Ext.resizer.SplitterTracker
127834  * @extends Ext.dd.DragTracker
127835  * Private utility class for Ext.Splitter.
127836  * @private
127837  */
127838 Ext.define('Ext.resizer.SplitterTracker', {
127839     extend: 'Ext.dd.DragTracker',
127840     requires: ['Ext.util.Region'],
127841     enabled: true,
127842     
127843     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
127844
127845     getPrevCmp: function() {
127846         var splitter = this.getSplitter();
127847         return splitter.previousSibling();
127848     },
127849
127850     getNextCmp: function() {
127851         var splitter = this.getSplitter();
127852         return splitter.nextSibling();
127853     },
127854
127855     // ensure the tracker is enabled, store boxes of previous and next
127856     // components and calculate the constrain region
127857     onBeforeStart: function(e) {
127858         var me = this,
127859             prevCmp = me.getPrevCmp(),
127860             nextCmp = me.getNextCmp(),
127861             collapseEl = me.getSplitter().collapseEl,
127862             overlay;
127863             
127864         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
127865             return false;
127866         }
127867
127868         // SplitterTracker is disabled if any of its adjacents are collapsed.
127869         if (nextCmp.collapsed || prevCmp.collapsed) {
127870             return false;
127871         }
127872         
127873         overlay = me.overlay =  Ext.getBody().createChild({
127874             cls: me.overlayCls, 
127875             html: '&#160;'
127876         });
127877         overlay.unselectable();
127878         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
127879         overlay.show();
127880         
127881         // store boxes of previous and next
127882         me.prevBox  = prevCmp.getEl().getBox();
127883         me.nextBox  = nextCmp.getEl().getBox();
127884         me.constrainTo = me.calculateConstrainRegion();
127885     },
127886
127887     // We move the splitter el. Add the proxy class.
127888     onStart: function(e) {
127889         var splitter = this.getSplitter();
127890         splitter.addCls(splitter.baseCls + '-active');
127891     },
127892
127893     // calculate the constrain Region in which the splitter el may be moved.
127894     calculateConstrainRegion: function() {
127895         var me         = this,
127896             splitter   = me.getSplitter(),
127897             splitWidth = splitter.getWidth(),
127898             defaultMin = splitter.defaultSplitMin,
127899             orient     = splitter.orientation,
127900             prevBox    = me.prevBox,
127901             prevCmp    = me.getPrevCmp(),
127902             nextBox    = me.nextBox,
127903             nextCmp    = me.getNextCmp(),
127904             // prev and nextConstrainRegions are the maximumBoxes minus the
127905             // minimumBoxes. The result is always the intersection
127906             // of these two boxes.
127907             prevConstrainRegion, nextConstrainRegion;
127908
127909         // vertical splitters, so resizing left to right
127910         if (orient === 'vertical') {
127911
127912             // Region constructor accepts (top, right, bottom, left)
127913             // anchored/calculated from the left
127914             prevConstrainRegion = Ext.create('Ext.util.Region',
127915                 prevBox.y,
127916                 // Right boundary is x + maxWidth if there IS a maxWidth.
127917                 // Otherwise it is calculated based upon the minWidth of the next Component
127918                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
127919                 prevBox.bottom,
127920                 prevBox.x + (prevCmp.minWidth || defaultMin)
127921             );
127922             // anchored/calculated from the right
127923             nextConstrainRegion = Ext.create('Ext.util.Region',
127924                 nextBox.y,
127925                 nextBox.right - (nextCmp.minWidth || defaultMin),
127926                 nextBox.bottom,
127927                 // Left boundary is right - maxWidth if there IS a maxWidth.
127928                 // Otherwise it is calculated based upon the minWidth of the previous Component
127929                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
127930             );
127931         } else {
127932             // anchored/calculated from the top
127933             prevConstrainRegion = Ext.create('Ext.util.Region',
127934                 prevBox.y + (prevCmp.minHeight || defaultMin),
127935                 prevBox.right,
127936                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
127937                 // Otherwise it is calculated based upon the minWidth of the next Component
127938                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
127939                 prevBox.x
127940             );
127941             // anchored/calculated from the bottom
127942             nextConstrainRegion = Ext.create('Ext.util.Region',
127943                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
127944                 // Otherwise it is calculated based upon the minHeight of the previous Component
127945                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
127946                 nextBox.right,
127947                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
127948                 nextBox.x
127949             );
127950         }
127951
127952         // intersection of the two regions to provide region draggable
127953         return prevConstrainRegion.intersect(nextConstrainRegion);
127954     },
127955
127956     // Performs the actual resizing of the previous and next components
127957     performResize: function(e) {
127958         var me       = this,
127959             offset   = me.getOffset('dragTarget'),
127960             splitter = me.getSplitter(),
127961             orient   = splitter.orientation,
127962             prevCmp  = me.getPrevCmp(),
127963             nextCmp  = me.getNextCmp(),
127964             owner    = splitter.ownerCt,
127965             layout   = owner.getLayout();
127966
127967         // Inhibit automatic container layout caused by setSize calls below.
127968         owner.suspendLayout = true;
127969
127970         if (orient === 'vertical') {
127971             if (prevCmp) {
127972                 if (!prevCmp.maintainFlex) {
127973                     delete prevCmp.flex;
127974                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
127975                 }
127976             }
127977             if (nextCmp) {
127978                 if (!nextCmp.maintainFlex) {
127979                     delete nextCmp.flex;
127980                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
127981                 }
127982             }
127983         // verticals
127984         } else {
127985             if (prevCmp) {
127986                 if (!prevCmp.maintainFlex) {
127987                     delete prevCmp.flex;
127988                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
127989                 }
127990             }
127991             if (nextCmp) {
127992                 if (!nextCmp.maintainFlex) {
127993                     delete nextCmp.flex;
127994                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
127995                 }
127996             }
127997         }
127998         delete owner.suspendLayout;
127999         layout.onLayout();
128000     },
128001
128002     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
128003     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
128004     // regardless (by onBeforeStart).
128005     endDrag: function () {
128006         var me = this;
128007
128008         if (me.overlay) {
128009              me.overlay.remove();
128010              delete me.overlay;
128011         }
128012
128013         me.callParent(arguments); // this calls onEnd
128014     },
128015
128016     // perform the resize and remove the proxy class from the splitter el
128017     onEnd: function(e) {
128018         var me = this,
128019             splitter = me.getSplitter();
128020             
128021         splitter.removeCls(splitter.baseCls + '-active');
128022         me.performResize();
128023     },
128024
128025     // Track the proxy and set the proper XY coordinates
128026     // while constraining the drag
128027     onDrag: function(e) {
128028         var me        = this,
128029             offset    = me.getOffset('dragTarget'),
128030             splitter  = me.getSplitter(),
128031             splitEl   = splitter.getEl(),
128032             orient    = splitter.orientation;
128033
128034         if (orient === "vertical") {
128035             splitEl.setX(me.startRegion.left + offset[0]);
128036         } else {
128037             splitEl.setY(me.startRegion.top + offset[1]);
128038         }
128039     },
128040
128041     getSplitter: function() {
128042         return Ext.getCmp(this.getDragCt().id);
128043     }
128044 });
128045 /**
128046  * @class Ext.selection.CellModel
128047  * @extends Ext.selection.Model
128048  */
128049 Ext.define('Ext.selection.CellModel', {
128050     extend: 'Ext.selection.Model',
128051     alias: 'selection.cellmodel',
128052     requires: ['Ext.util.KeyNav'],
128053
128054     /**
128055      * @cfg {Boolean} enableKeyNav
128056      * Turns on/off keyboard navigation within the grid.
128057      */
128058     enableKeyNav: true,
128059
128060     /**
128061      * @cfg {Boolean} preventWrap
128062      * Set this configuration to true to prevent wrapping around of selection as
128063      * a user navigates to the first or last column.
128064      */
128065     preventWrap: false,
128066
128067     constructor: function(){
128068         this.addEvents(
128069             /**
128070              * @event deselect
128071              * Fired after a cell is deselected
128072              * @param {Ext.selection.CellModel} this
128073              * @param {Ext.data.Model} record The record of the deselected cell
128074              * @param {Number} row The row index deselected
128075              * @param {Number} column The column index deselected
128076              */
128077             'deselect',
128078
128079             /**
128080              * @event select
128081              * Fired after a cell is selected
128082              * @param {Ext.selection.CellModel} this
128083              * @param {Ext.data.Model} record The record of the selected cell
128084              * @param {Number} row The row index selected
128085              * @param {Number} column The column index selected
128086              */
128087             'select'
128088         );
128089         this.callParent(arguments);
128090     },
128091
128092     bindComponent: function(view) {
128093         var me = this;
128094         me.primaryView = view;
128095         me.views = me.views || [];
128096         me.views.push(view);
128097         me.bind(view.getStore(), true);
128098
128099         view.on({
128100             cellmousedown: me.onMouseDown,
128101             refresh: me.onViewRefresh,
128102             scope: me
128103         });
128104
128105         if (me.enableKeyNav) {
128106             me.initKeyNav(view);
128107         }
128108     },
128109
128110     initKeyNav: function(view) {
128111         var me = this;
128112
128113         if (!view.rendered) {
128114             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128115             return;
128116         }
128117
128118         view.el.set({
128119             tabIndex: -1
128120         });
128121
128122         // view.el has tabIndex -1 to allow for
128123         // keyboard events to be passed to it.
128124         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
128125             up: me.onKeyUp,
128126             down: me.onKeyDown,
128127             right: me.onKeyRight,
128128             left: me.onKeyLeft,
128129             tab: me.onKeyTab,
128130             scope: me
128131         });
128132     },
128133
128134     getHeaderCt: function() {
128135         return this.primaryView.headerCt;
128136     },
128137
128138     onKeyUp: function(e, t) {
128139         this.move('up', e);
128140     },
128141
128142     onKeyDown: function(e, t) {
128143         this.move('down', e);
128144     },
128145
128146     onKeyLeft: function(e, t) {
128147         this.move('left', e);
128148     },
128149
128150     onKeyRight: function(e, t) {
128151         this.move('right', e);
128152     },
128153
128154     move: function(dir, e) {
128155         var me = this,
128156             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
128157         if (pos) {
128158             me.setCurrentPosition(pos);
128159         }
128160         return pos;
128161     },
128162
128163     /**
128164      * Returns the current position in the format {row: row, column: column}
128165      */
128166     getCurrentPosition: function() {
128167         return this.position;
128168     },
128169
128170     /**
128171      * Sets the current position
128172      * @param {Object} position The position to set.
128173      */
128174     setCurrentPosition: function(pos) {
128175         var me = this;
128176
128177         if (me.position) {
128178             me.onCellDeselect(me.position);
128179         }
128180         if (pos) {
128181             me.onCellSelect(pos);
128182         }
128183         me.position = pos;
128184     },
128185
128186     /**
128187      * Set the current position based on where the user clicks.
128188      * @private
128189      */
128190     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
128191         this.setCurrentPosition({
128192             row: rowIndex,
128193             column: cellIndex
128194         });
128195     },
128196
128197     // notify the view that the cell has been selected to update the ui
128198     // appropriately and bring the cell into focus
128199     onCellSelect: function(position) {
128200         var me = this,
128201             store = me.view.getStore(),
128202             record = store.getAt(position.row);
128203
128204         me.doSelect(record);
128205         me.primaryView.onCellSelect(position);
128206         // TODO: Remove temporary cellFocus call here.
128207         me.primaryView.onCellFocus(position);
128208         me.fireEvent('select', me, record, position.row, position.column);
128209     },
128210
128211     // notify view that the cell has been deselected to update the ui
128212     // appropriately
128213     onCellDeselect: function(position) {
128214         var me = this,
128215             store = me.view.getStore(),
128216             record = store.getAt(position.row);
128217
128218         me.doDeselect(record);
128219         me.primaryView.onCellDeselect(position);
128220         me.fireEvent('deselect', me, record, position.row, position.column);
128221     },
128222
128223     onKeyTab: function(e, t) {
128224         var me = this,
128225             direction = e.shiftKey ? 'left' : 'right',
128226             editingPlugin = me.view.editingPlugin,
128227             position = me.move(direction, e);
128228
128229         if (editingPlugin && position && me.wasEditing) {
128230             editingPlugin.startEditByPosition(position);
128231         }
128232         delete me.wasEditing;
128233     },
128234
128235     onEditorTab: function(editingPlugin, e) {
128236         var me = this,
128237             direction = e.shiftKey ? 'left' : 'right',
128238             position  = me.move(direction, e);
128239
128240         if (position) {
128241             editingPlugin.startEditByPosition(position);
128242             me.wasEditing = true;
128243         }
128244     },
128245
128246     refresh: function() {
128247         var pos = this.getCurrentPosition();
128248         if (pos) {
128249             this.onCellSelect(pos);
128250         }
128251     },
128252
128253     onViewRefresh: function() {
128254         var pos = this.getCurrentPosition();
128255         if (pos) {
128256             this.onCellDeselect(pos);
128257             this.setCurrentPosition(null);
128258         }
128259     },
128260
128261     selectByPosition: function(position) {
128262         this.setCurrentPosition(position);
128263     }
128264 });
128265 /**
128266  * @class Ext.selection.RowModel
128267  * @extends Ext.selection.Model
128268  */
128269 Ext.define('Ext.selection.RowModel', {
128270     extend: 'Ext.selection.Model',
128271     alias: 'selection.rowmodel',
128272     requires: ['Ext.util.KeyNav'],
128273
128274     /**
128275      * @private
128276      * Number of pixels to scroll to the left/right when pressing
128277      * left/right keys.
128278      */
128279     deltaScroll: 5,
128280
128281     /**
128282      * @cfg {Boolean} enableKeyNav
128283      *
128284      * Turns on/off keyboard navigation within the grid.
128285      */
128286     enableKeyNav: true,
128287     
128288     /**
128289      * @cfg {Boolean} [ignoreRightMouseSelection=true]
128290      * True to ignore selections that are made when using the right mouse button if there are
128291      * records that are already selected. If no records are selected, selection will continue 
128292      * as normal
128293      */
128294     ignoreRightMouseSelection: true,
128295
128296     constructor: function(){
128297         this.addEvents(
128298             /**
128299              * @event beforedeselect
128300              * Fired before a record is deselected. If any listener returns false, the
128301              * deselection is cancelled.
128302              * @param {Ext.selection.RowModel} this
128303              * @param {Ext.data.Model} record The deselected record
128304              * @param {Number} index The row index deselected
128305              */
128306             'beforedeselect',
128307
128308             /**
128309              * @event beforeselect
128310              * Fired before a record is selected. If any listener returns false, the
128311              * selection is cancelled.
128312              * @param {Ext.selection.RowModel} this
128313              * @param {Ext.data.Model} record The selected record
128314              * @param {Number} index The row index selected
128315              */
128316             'beforeselect',
128317
128318             /**
128319              * @event deselect
128320              * Fired after a record is deselected
128321              * @param {Ext.selection.RowModel} this
128322              * @param {Ext.data.Model} record The deselected record
128323              * @param {Number} index The row index deselected
128324              */
128325             'deselect',
128326
128327             /**
128328              * @event select
128329              * Fired after a record is selected
128330              * @param {Ext.selection.RowModel} this
128331              * @param {Ext.data.Model} record The selected record
128332              * @param {Number} index The row index selected
128333              */
128334             'select'
128335         );
128336         this.callParent(arguments);
128337     },
128338
128339     bindComponent: function(view) {
128340         var me = this;
128341
128342         me.views = me.views || [];
128343         me.views.push(view);
128344         me.bind(view.getStore(), true);
128345
128346         view.on({
128347             itemmousedown: me.onRowMouseDown,
128348             scope: me
128349         });
128350
128351         if (me.enableKeyNav) {
128352             me.initKeyNav(view);
128353         }
128354     },
128355
128356     initKeyNav: function(view) {
128357         var me = this;
128358
128359         if (!view.rendered) {
128360             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128361             return;
128362         }
128363
128364         view.el.set({
128365             tabIndex: -1
128366         });
128367
128368         // view.el has tabIndex -1 to allow for
128369         // keyboard events to be passed to it.
128370         me.keyNav = new Ext.util.KeyNav(view.el, {
128371             up: me.onKeyUp,
128372             down: me.onKeyDown,
128373             right: me.onKeyRight,
128374             left: me.onKeyLeft,
128375             pageDown: me.onKeyPageDown,
128376             pageUp: me.onKeyPageUp,
128377             home: me.onKeyHome,
128378             end: me.onKeyEnd,
128379             scope: me
128380         });
128381         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
128382     },
128383
128384     // Returns the number of rows currently visible on the screen or
128385     // false if there were no rows. This assumes that all rows are
128386     // of the same height and the first view is accurate.
128387     getRowsVisible: function() {
128388         var rowsVisible = false,
128389             view = this.views[0],
128390             row = view.getNode(0),
128391             rowHeight, gridViewHeight;
128392
128393         if (row) {
128394             rowHeight = Ext.fly(row).getHeight();
128395             gridViewHeight = view.el.getHeight();
128396             rowsVisible = Math.floor(gridViewHeight / rowHeight);
128397         }
128398
128399         return rowsVisible;
128400     },
128401
128402     // go to last visible record in grid.
128403     onKeyEnd: function(e, t) {
128404         var me = this,
128405             last = me.store.getAt(me.store.getCount() - 1);
128406
128407         if (last) {
128408             if (e.shiftKey) {
128409                 me.selectRange(last, me.lastFocused || 0);
128410                 me.setLastFocused(last);
128411             } else if (e.ctrlKey) {
128412                 me.setLastFocused(last);
128413             } else {
128414                 me.doSelect(last);
128415             }
128416         }
128417     },
128418
128419     // go to first visible record in grid.
128420     onKeyHome: function(e, t) {
128421         var me = this,
128422             first = me.store.getAt(0);
128423
128424         if (first) {
128425             if (e.shiftKey) {
128426                 me.selectRange(first, me.lastFocused || 0);
128427                 me.setLastFocused(first);
128428             } else if (e.ctrlKey) {
128429                 me.setLastFocused(first);
128430             } else {
128431                 me.doSelect(first, false);
128432             }
128433         }
128434     },
128435
128436     // Go one page up from the lastFocused record in the grid.
128437     onKeyPageUp: function(e, t) {
128438         var me = this,
128439             rowsVisible = me.getRowsVisible(),
128440             selIdx,
128441             prevIdx,
128442             prevRecord,
128443             currRec;
128444
128445         if (rowsVisible) {
128446             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128447             prevIdx = selIdx - rowsVisible;
128448             if (prevIdx < 0) {
128449                 prevIdx = 0;
128450             }
128451             prevRecord = me.store.getAt(prevIdx);
128452             if (e.shiftKey) {
128453                 currRec = me.store.getAt(selIdx);
128454                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
128455                 me.setLastFocused(prevRecord);
128456             } else if (e.ctrlKey) {
128457                 e.preventDefault();
128458                 me.setLastFocused(prevRecord);
128459             } else {
128460                 me.doSelect(prevRecord);
128461             }
128462
128463         }
128464     },
128465
128466     // Go one page down from the lastFocused record in the grid.
128467     onKeyPageDown: function(e, t) {
128468         var me = this,
128469             rowsVisible = me.getRowsVisible(),
128470             selIdx,
128471             nextIdx,
128472             nextRecord,
128473             currRec;
128474
128475         if (rowsVisible) {
128476             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128477             nextIdx = selIdx + rowsVisible;
128478             if (nextIdx >= me.store.getCount()) {
128479                 nextIdx = me.store.getCount() - 1;
128480             }
128481             nextRecord = me.store.getAt(nextIdx);
128482             if (e.shiftKey) {
128483                 currRec = me.store.getAt(selIdx);
128484                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
128485                 me.setLastFocused(nextRecord);
128486             } else if (e.ctrlKey) {
128487                 // some browsers, this means go thru browser tabs
128488                 // attempt to stop.
128489                 e.preventDefault();
128490                 me.setLastFocused(nextRecord);
128491             } else {
128492                 me.doSelect(nextRecord);
128493             }
128494         }
128495     },
128496
128497     // Select/Deselect based on pressing Spacebar.
128498     // Assumes a SIMPLE selectionmode style
128499     onKeyPress: function(e, t) {
128500         if (e.getKey() === e.SPACE) {
128501             e.stopEvent();
128502             var me = this,
128503                 record = me.lastFocused;
128504
128505             if (record) {
128506                 if (me.isSelected(record)) {
128507                     me.doDeselect(record, false);
128508                 } else {
128509                     me.doSelect(record, true);
128510                 }
128511             }
128512         }
128513     },
128514
128515     // Navigate one record up. This could be a selection or
128516     // could be simply focusing a record for discontiguous
128517     // selection. Provides bounds checking.
128518     onKeyUp: function(e, t) {
128519         var me = this,
128520             view = me.views[0],
128521             idx  = me.store.indexOf(me.lastFocused),
128522             record;
128523
128524         if (idx > 0) {
128525             // needs to be the filtered count as thats what
128526             // will be visible.
128527             record = me.store.getAt(idx - 1);
128528             if (e.shiftKey && me.lastFocused) {
128529                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128530                     me.doDeselect(me.lastFocused, true);
128531                     me.setLastFocused(record);
128532                 } else if (!me.isSelected(me.lastFocused)) {
128533                     me.doSelect(me.lastFocused, true);
128534                     me.doSelect(record, true);
128535                 } else {
128536                     me.doSelect(record, true);
128537                 }
128538             } else if (e.ctrlKey) {
128539                 me.setLastFocused(record);
128540             } else {
128541                 me.doSelect(record);
128542                 //view.focusRow(idx - 1);
128543             }
128544         }
128545         // There was no lastFocused record, and the user has pressed up
128546         // Ignore??
128547         //else if (this.selected.getCount() == 0) {
128548         //
128549         //    this.doSelect(record);
128550         //    //view.focusRow(idx - 1);
128551         //}
128552     },
128553
128554     // Navigate one record down. This could be a selection or
128555     // could be simply focusing a record for discontiguous
128556     // selection. Provides bounds checking.
128557     onKeyDown: function(e, t) {
128558         var me = this,
128559             view = me.views[0],
128560             idx  = me.store.indexOf(me.lastFocused),
128561             record;
128562
128563         // needs to be the filtered count as thats what
128564         // will be visible.
128565         if (idx + 1 < me.store.getCount()) {
128566             record = me.store.getAt(idx + 1);
128567             if (me.selected.getCount() === 0) {
128568                 me.doSelect(record);
128569                 //view.focusRow(idx + 1);
128570             } else if (e.shiftKey && me.lastFocused) {
128571                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128572                     me.doDeselect(me.lastFocused, true);
128573                     me.setLastFocused(record);
128574                 } else if (!me.isSelected(me.lastFocused)) {
128575                     me.doSelect(me.lastFocused, true);
128576                     me.doSelect(record, true);
128577                 } else {
128578                     me.doSelect(record, true);
128579                 }
128580             } else if (e.ctrlKey) {
128581                 me.setLastFocused(record);
128582             } else {
128583                 me.doSelect(record);
128584                 //view.focusRow(idx + 1);
128585             }
128586         }
128587     },
128588
128589     scrollByDeltaX: function(delta) {
128590         var view    = this.views[0],
128591             section = view.up(),
128592             hScroll = section.horizontalScroller;
128593
128594         if (hScroll) {
128595             hScroll.scrollByDeltaX(delta);
128596         }
128597     },
128598
128599     onKeyLeft: function(e, t) {
128600         this.scrollByDeltaX(-this.deltaScroll);
128601     },
128602
128603     onKeyRight: function(e, t) {
128604         this.scrollByDeltaX(this.deltaScroll);
128605     },
128606
128607     // Select the record with the event included so that
128608     // we can take into account ctrlKey, shiftKey, etc
128609     onRowMouseDown: function(view, record, item, index, e) {
128610         view.el.focus();
128611         if (!this.allowRightMouseSelection(e)) {
128612             return;
128613         }
128614         this.selectWithEvent(record, e);
128615     },
128616     
128617     /**
128618      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
128619      * option.
128620      * @private
128621      * @param {Ext.EventObject} e The event
128622      * @return {Boolean} False if the selection should not proceed
128623      */
128624     allowRightMouseSelection: function(e) {
128625         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
128626         if (disallow) {
128627             disallow = this.hasSelection();
128628         }
128629         return !disallow;
128630     },
128631
128632     // Allow the GridView to update the UI by
128633     // adding/removing a CSS class from the row.
128634     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
128635         var me      = this,
128636             views   = me.views,
128637             viewsLn = views.length,
128638             store   = me.store,
128639             rowIdx  = store.indexOf(record),
128640             eventName = isSelected ? 'select' : 'deselect',
128641             i = 0;
128642
128643         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
128644                 commitFn() !== false) {
128645
128646             for (; i < viewsLn; i++) {
128647                 if (isSelected) {
128648                     views[i].onRowSelect(rowIdx, suppressEvent);
128649                 } else {
128650                     views[i].onRowDeselect(rowIdx, suppressEvent);
128651                 }
128652             }
128653
128654             if (!suppressEvent) {
128655                 me.fireEvent(eventName, me, record, rowIdx);
128656             }
128657         }
128658     },
128659
128660     // Provide indication of what row was last focused via
128661     // the gridview.
128662     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
128663         var views   = this.views,
128664             viewsLn = views.length,
128665             store   = this.store,
128666             rowIdx,
128667             i = 0;
128668
128669         if (oldFocused) {
128670             rowIdx = store.indexOf(oldFocused);
128671             if (rowIdx != -1) {
128672                 for (; i < viewsLn; i++) {
128673                     views[i].onRowFocus(rowIdx, false);
128674                 }
128675             }
128676         }
128677
128678         if (newFocused) {
128679             rowIdx = store.indexOf(newFocused);
128680             if (rowIdx != -1) {
128681                 for (i = 0; i < viewsLn; i++) {
128682                     views[i].onRowFocus(rowIdx, true, supressFocus);
128683                 }
128684             }
128685         }
128686     },
128687
128688     onEditorTab: function(editingPlugin, e) {
128689         var me = this,
128690             view = me.views[0],
128691             record = editingPlugin.getActiveRecord(),
128692             header = editingPlugin.getActiveColumn(),
128693             position = view.getPosition(record, header),
128694             direction = e.shiftKey ? 'left' : 'right',
128695             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
128696
128697         if (newPosition) {
128698             editingPlugin.startEditByPosition(newPosition);
128699         }
128700     },
128701
128702     selectByPosition: function(position) {
128703         var record = this.store.getAt(position.row);
128704         this.select(record);
128705     }
128706 });
128707 /**
128708  * @class Ext.selection.CheckboxModel
128709  * @extends Ext.selection.RowModel
128710  *
128711  * A selection model that renders a column of checkboxes that can be toggled to
128712  * select or deselect rows. The default mode for this selection model is MULTI.
128713  *
128714  * The selection model will inject a header for the checkboxes in the first view
128715  * and according to the 'injectCheckbox' configuration.
128716  */
128717 Ext.define('Ext.selection.CheckboxModel', {
128718     alias: 'selection.checkboxmodel',
128719     extend: 'Ext.selection.RowModel',
128720
128721     /**
128722      * @cfg {String} mode
128723      * Modes of selection.
128724      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
128725      */
128726     mode: 'MULTI',
128727
128728     /**
128729      * @cfg {Number/Boolean/String} injectCheckbox
128730      * Instructs the SelectionModel whether or not to inject the checkbox header
128731      * automatically or not. (Note: By not placing the checkbox in manually, the
128732      * grid view will need to be rendered 2x on initial render.)
128733      * Supported values are a Number index, false and the strings 'first' and 'last'.
128734      */
128735     injectCheckbox: 0,
128736
128737     /**
128738      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
128739      * checkbox column.
128740      */
128741     checkOnly: false,
128742
128743     headerWidth: 24,
128744
128745     // private
128746     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
128747
128748     bindComponent: function(view) {
128749         var me = this;
128750
128751         me.sortable = false;
128752         me.callParent(arguments);
128753         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
128754             // if we have a locked header, only hook up to the first
128755             view.headerCt.on('headerclick', me.onHeaderClick, me);
128756             me.addCheckbox(true);
128757             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
128758         }
128759     },
128760
128761     hasLockedHeader: function(){
128762         var hasLocked = false;
128763         Ext.each(this.views, function(view){
128764             if (view.headerCt.lockedCt) {
128765                 hasLocked = true;
128766                 return false;
128767             }
128768         });
128769         return hasLocked;
128770     },
128771
128772     /**
128773      * Add the header checkbox to the header row
128774      * @private
128775      * @param {Boolean} initial True if we're binding for the first time.
128776      */
128777     addCheckbox: function(initial){
128778         var me = this,
128779             checkbox = me.injectCheckbox,
128780             view = me.views[0],
128781             headerCt = view.headerCt;
128782
128783         if (checkbox !== false) {
128784             if (checkbox == 'first') {
128785                 checkbox = 0;
128786             } else if (checkbox == 'last') {
128787                 checkbox = headerCt.getColumnCount();
128788             }
128789             headerCt.add(checkbox,  me.getHeaderConfig());
128790         }
128791
128792         if (initial !== true) {
128793             view.refresh();
128794         }
128795     },
128796
128797     /**
128798      * Toggle the ui header between checked and unchecked state.
128799      * @param {Boolean} isChecked
128800      * @private
128801      */
128802     toggleUiHeader: function(isChecked) {
128803         var view     = this.views[0],
128804             headerCt = view.headerCt,
128805             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
128806
128807         if (checkHd) {
128808             if (isChecked) {
128809                 checkHd.el.addCls(this.checkerOnCls);
128810             } else {
128811                 checkHd.el.removeCls(this.checkerOnCls);
128812             }
128813         }
128814     },
128815
128816     /**
128817      * Toggle between selecting all and deselecting all when clicking on
128818      * a checkbox header.
128819      */
128820     onHeaderClick: function(headerCt, header, e) {
128821         if (header.isCheckerHd) {
128822             e.stopEvent();
128823             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
128824             if (isChecked) {
128825                 // We have to supress the event or it will scrollTo the change
128826                 this.deselectAll(true);
128827             } else {
128828                 // We have to supress the event or it will scrollTo the change
128829                 this.selectAll(true);
128830             }
128831         }
128832     },
128833
128834     /**
128835      * Retrieve a configuration to be used in a HeaderContainer.
128836      * This should be used when injectCheckbox is set to false.
128837      */
128838     getHeaderConfig: function() {
128839         var me = this;
128840
128841         return {
128842             isCheckerHd: true,
128843             text : '&#160;',
128844             width: me.headerWidth,
128845             sortable: false,
128846             draggable: false,
128847             resizable: false,
128848             hideable: false,
128849             menuDisabled: true,
128850             dataIndex: '',
128851             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
128852             renderer: Ext.Function.bind(me.renderer, me),
128853             locked: me.hasLockedHeader()
128854         };
128855     },
128856
128857     /**
128858      * Generates the HTML to be rendered in the injected checkbox column for each row.
128859      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
128860      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
128861      */
128862     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
128863         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
128864         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
128865     },
128866
128867     // override
128868     onRowMouseDown: function(view, record, item, index, e) {
128869         view.el.focus();
128870         var me = this,
128871             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
128872             
128873         if (!me.allowRightMouseSelection(e)) {
128874             return;
128875         }
128876
128877         // checkOnly set, but we didn't click on a checker.
128878         if (me.checkOnly && !checker) {
128879             return;
128880         }
128881
128882         if (checker) {
128883             var mode = me.getSelectionMode();
128884             // dont change the mode if its single otherwise
128885             // we would get multiple selection
128886             if (mode !== 'SINGLE') {
128887                 me.setSelectionMode('SIMPLE');
128888             }
128889             me.selectWithEvent(record, e);
128890             me.setSelectionMode(mode);
128891         } else {
128892             me.selectWithEvent(record, e);
128893         }
128894     },
128895
128896     /**
128897      * Synchronize header checker value as selection changes.
128898      * @private
128899      */
128900     onSelectChange: function() {
128901         this.callParent(arguments);
128902
128903         // check to see if all records are selected
128904         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
128905         this.toggleUiHeader(hdSelectStatus);
128906     }
128907 });
128908
128909 /**
128910  * @class Ext.selection.TreeModel
128911  * @extends Ext.selection.RowModel
128912  *
128913  * Adds custom behavior for left/right keyboard navigation for use with a tree.
128914  * Depends on the view having an expand and collapse method which accepts a
128915  * record.
128916  * 
128917  * @private
128918  */
128919 Ext.define('Ext.selection.TreeModel', {
128920     extend: 'Ext.selection.RowModel',
128921     alias: 'selection.treemodel',
128922     
128923     // typically selection models prune records from the selection
128924     // model when they are removed, because the TreeView constantly
128925     // adds/removes records as they are expanded/collapsed
128926     pruneRemoved: false,
128927     
128928     onKeyRight: function(e, t) {
128929         var focused = this.getLastFocused(),
128930             view    = this.view;
128931             
128932         if (focused) {
128933             // tree node is already expanded, go down instead
128934             // this handles both the case where we navigate to firstChild and if
128935             // there are no children to the nextSibling
128936             if (focused.isExpanded()) {
128937                 this.onKeyDown(e, t);
128938             // if its not a leaf node, expand it
128939             } else if (!focused.isLeaf()) {
128940                 view.expand(focused);
128941             }
128942         }
128943     },
128944     
128945     onKeyLeft: function(e, t) {
128946         var focused = this.getLastFocused(),
128947             view    = this.view,
128948             viewSm  = view.getSelectionModel(),
128949             parentNode, parentRecord;
128950
128951         if (focused) {
128952             parentNode = focused.parentNode;
128953             // if focused node is already expanded, collapse it
128954             if (focused.isExpanded()) {
128955                 view.collapse(focused);
128956             // has a parentNode and its not root
128957             // TODO: this needs to cover the case where the root isVisible
128958             } else if (parentNode && !parentNode.isRoot()) {
128959                 // Select a range of records when doing multiple selection.
128960                 if (e.shiftKey) {
128961                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
128962                     viewSm.setLastFocused(parentNode);
128963                 // just move focus, not selection
128964                 } else if (e.ctrlKey) {
128965                     viewSm.setLastFocused(parentNode);
128966                 // select it
128967                 } else {
128968                     viewSm.select(parentNode);
128969                 }
128970             }
128971         }
128972     },
128973     
128974     onKeyPress: function(e, t) {
128975         var key = e.getKey(),
128976             selected, 
128977             checked;
128978         
128979         if (key === e.SPACE || key === e.ENTER) {
128980             e.stopEvent();
128981             selected = this.getLastSelected();
128982             if (selected) {
128983                 this.view.onCheckChange(selected);
128984             }
128985         } else {
128986             this.callParent(arguments);
128987         }
128988     }
128989 });
128990
128991 /**
128992  * @class Ext.slider.Thumb
128993  * @extends Ext.Base
128994  * @private
128995  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
128996  * be created internally by an {@link Ext.slider.Multi Multi slider}.
128997  */
128998 Ext.define('Ext.slider.Thumb', {
128999     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
129000     /**
129001      * @private
129002      * @property {Number} topThumbZIndex
129003      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
129004      */
129005     topZIndex: 10000,
129006
129007     /**
129008      * @cfg {Ext.slider.MultiSlider} slider (required)
129009      * The Slider to render to.
129010      */
129011
129012     /**
129013      * Creates new slider thumb.
129014      * @param {Object} config (optional) Config object.
129015      */
129016     constructor: function(config) {
129017         var me = this;
129018
129019         /**
129020          * @property {Ext.slider.MultiSlider} slider
129021          * The slider this thumb is contained within
129022          */
129023         Ext.apply(me, config || {}, {
129024             cls: Ext.baseCSSPrefix + 'slider-thumb',
129025
129026             /**
129027              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
129028              */
129029             constrain: false
129030         });
129031         me.callParent([config]);
129032
129033         if (me.slider.vertical) {
129034             Ext.apply(me, Ext.slider.Thumb.Vertical);
129035         }
129036     },
129037
129038     /**
129039      * Renders the thumb into a slider
129040      */
129041     render: function() {
129042         var me = this;
129043
129044         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
129045         if (me.disabled) {
129046             me.disable();
129047         }
129048         me.initEvents();
129049     },
129050
129051     /**
129052      * @private
129053      * move the thumb
129054      */
129055     move: function(v, animate){
129056         if(!animate){
129057             this.el.setLeft(v);
129058         }else{
129059             Ext.create('Ext.fx.Anim', {
129060                 target: this.el,
129061                 duration: 350,
129062                 to: {
129063                     left: v
129064                 }
129065             });
129066         }
129067     },
129068
129069     /**
129070      * @private
129071      * Bring thumb dom element to front.
129072      */
129073     bringToFront: function() {
129074         this.el.setStyle('zIndex', this.topZIndex);
129075     },
129076
129077     /**
129078      * @private
129079      * Send thumb dom element to back.
129080      */
129081     sendToBack: function() {
129082         this.el.setStyle('zIndex', '');
129083     },
129084
129085     /**
129086      * Enables the thumb if it is currently disabled
129087      */
129088     enable: function() {
129089         var me = this;
129090
129091         me.disabled = false;
129092         if (me.el) {
129093             me.el.removeCls(me.slider.disabledCls);
129094         }
129095     },
129096
129097     /**
129098      * Disables the thumb if it is currently enabled
129099      */
129100     disable: function() {
129101         var me = this;
129102
129103         me.disabled = true;
129104         if (me.el) {
129105             me.el.addCls(me.slider.disabledCls);
129106         }
129107     },
129108
129109     /**
129110      * Sets up an Ext.dd.DragTracker for this thumb
129111      */
129112     initEvents: function() {
129113         var me = this,
129114             el = me.el;
129115
129116         me.tracker = Ext.create('Ext.dd.DragTracker', {
129117             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
129118             onStart      : Ext.Function.bind(me.onDragStart, me),
129119             onDrag       : Ext.Function.bind(me.onDrag, me),
129120             onEnd        : Ext.Function.bind(me.onDragEnd, me),
129121             tolerance    : 3,
129122             autoStart    : 300,
129123             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
129124         });
129125
129126         me.tracker.initEl(el);
129127     },
129128
129129     /**
129130      * @private
129131      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
129132      * this returns false to disable the DragTracker too.
129133      * @return {Boolean} False if the slider is currently disabled
129134      */
129135     onBeforeDragStart : function(e) {
129136         if (this.disabled) {
129137             return false;
129138         } else {
129139             this.slider.promoteThumb(this);
129140             return true;
129141         }
129142     },
129143
129144     /**
129145      * @private
129146      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
129147      * to the thumb and fires the 'dragstart' event
129148      */
129149     onDragStart: function(e){
129150         var me = this;
129151
129152         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129153         me.dragging = true;
129154         me.dragStartValue = me.value;
129155
129156         me.slider.fireEvent('dragstart', me.slider, e, me);
129157     },
129158
129159     /**
129160      * @private
129161      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
129162      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
129163      */
129164     onDrag: function(e) {
129165         var me       = this,
129166             slider   = me.slider,
129167             index    = me.index,
129168             newValue = me.getNewValue(),
129169             above,
129170             below;
129171
129172         if (me.constrain) {
129173             above = slider.thumbs[index + 1];
129174             below = slider.thumbs[index - 1];
129175
129176             if (below !== undefined && newValue <= below.value) {
129177                 newValue = below.value;
129178             }
129179
129180             if (above !== undefined && newValue >= above.value) {
129181                 newValue = above.value;
129182             }
129183         }
129184
129185         slider.setValue(index, newValue, false);
129186         slider.fireEvent('drag', slider, e, me);
129187     },
129188
129189     getNewValue: function() {
129190         var slider = this.slider,
129191             pos = slider.innerEl.translatePoints(this.tracker.getXY());
129192
129193         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
129194     },
129195
129196     /**
129197      * @private
129198      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
129199      * fires the 'changecomplete' event with the new value
129200      */
129201     onDragEnd: function(e) {
129202         var me     = this,
129203             slider = me.slider,
129204             value  = me.value;
129205
129206         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129207
129208         me.dragging = false;
129209         slider.fireEvent('dragend', slider, e);
129210
129211         if (me.dragStartValue != value) {
129212             slider.fireEvent('changecomplete', slider, value, me);
129213         }
129214     },
129215
129216     destroy: function() {
129217         Ext.destroy(this.tracker);
129218     },
129219     statics: {
129220         // Method overrides to support vertical dragging of thumb within slider
129221         Vertical: {
129222             getNewValue: function() {
129223                 var slider   = this.slider,
129224                     innerEl  = slider.innerEl,
129225                     pos      = innerEl.translatePoints(this.tracker.getXY()),
129226                     bottom   = innerEl.getHeight() - pos.top;
129227
129228                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
129229             },
129230             move: function(v, animate) {
129231                 if (!animate) {
129232                     this.el.setBottom(v);
129233                 } else {
129234                     Ext.create('Ext.fx.Anim', {
129235                         target: this.el,
129236                         duration: 350,
129237                         to: {
129238                             bottom: v
129239                         }
129240                     });
129241                 }
129242             }
129243         }
129244     }
129245 });
129246
129247 /**
129248  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
129249  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
129250  * options to the slider directly.
129251  *
129252  *     @example
129253  *     Ext.create('Ext.slider.Single', {
129254  *         width: 214,
129255  *         minValue: 0,
129256  *         maxValue: 100,
129257  *         useTips: true,
129258  *         renderTo: Ext.getBody()
129259  *     });
129260  *
129261  * Optionally provide your own tip text by passing tipText:
129262  *
129263  *     @example
129264  *     Ext.create('Ext.slider.Single', {
129265  *         width: 214,
129266  *         minValue: 0,
129267  *         maxValue: 100,
129268  *         useTips: true,
129269  *         tipText: function(thumb){
129270  *             return Ext.String.format('**{0}% complete**', thumb.value);
129271  *         },
129272  *         renderTo: Ext.getBody()
129273  *     });
129274  */
129275 Ext.define('Ext.slider.Tip', {
129276     extend: 'Ext.tip.Tip',
129277     minWidth: 10,
129278     alias: 'widget.slidertip',
129279     offsets : [0, -10],
129280
129281     isSliderTip: true,
129282
129283     init: function(slider) {
129284         var me = this;
129285
129286         slider.on({
129287             scope    : me,
129288             dragstart: me.onSlide,
129289             drag     : me.onSlide,
129290             dragend  : me.hide,
129291             destroy  : me.destroy
129292         });
129293     },
129294     /**
129295      * @private
129296      * Called whenever a dragstart or drag event is received on the associated Thumb.
129297      * Aligns the Tip with the Thumb's new position.
129298      * @param {Ext.slider.MultiSlider} slider The slider
129299      * @param {Ext.EventObject} e The Event object
129300      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
129301      */
129302     onSlide : function(slider, e, thumb) {
129303         var me = this;
129304         me.show();
129305         me.update(me.getText(thumb));
129306         me.doComponentLayout();
129307         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
129308     },
129309
129310     /**
129311      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
129312      * Thumb that the Tip is attached to. Override to customize.
129313      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
129314      * @return {String} The text to display in the tip
129315      */
129316     getText : function(thumb) {
129317         return String(thumb.value);
129318     }
129319 });
129320 /**
129321  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
129322  * and animation. Can be added as an item to any container.
129323  *
129324  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
129325  *
129326  *     @example
129327  *     Ext.create('Ext.slider.Multi', {
129328  *         width: 200,
129329  *         values: [25, 50, 75],
129330  *         increment: 5,
129331  *         minValue: 0,
129332  *         maxValue: 100,
129333  *
129334  *         // this defaults to true, setting to false allows the thumbs to pass each other
129335  *         constrainThumbs: false,
129336  *         renderTo: Ext.getBody()
129337  *     });
129338  */
129339 Ext.define('Ext.slider.Multi', {
129340     extend: 'Ext.form.field.Base',
129341     alias: 'widget.multislider',
129342     alternateClassName: 'Ext.slider.MultiSlider',
129343
129344     requires: [
129345         'Ext.slider.Thumb',
129346         'Ext.slider.Tip',
129347         'Ext.Number',
129348         'Ext.util.Format',
129349         'Ext.Template',
129350         'Ext.layout.component.field.Slider'
129351     ],
129352
129353     // note: {id} here is really {inputId}, but {cmpId} is available
129354     fieldSubTpl: [
129355         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
129356             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
129357                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
129358                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
129359                 '</div>',
129360             '</div>',
129361         '</div>',
129362         {
129363             disableFormats: true,
129364             compiled: true
129365         }
129366     ],
129367
129368     /**
129369      * @cfg {Number} value
129370      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
129371      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
129372      */
129373
129374     /**
129375      * @cfg {Number[]} values
129376      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
129377      * in this array. This will take precedence over the single {@link #value} config.
129378      */
129379
129380     /**
129381      * @cfg {Boolean} vertical
129382      * Orient the Slider vertically rather than horizontally.
129383      */
129384     vertical: false,
129385
129386     /**
129387      * @cfg {Number} minValue
129388      * The minimum value for the Slider.
129389      */
129390     minValue: 0,
129391
129392     /**
129393      * @cfg {Number} maxValue
129394      * The maximum value for the Slider.
129395      */
129396     maxValue: 100,
129397
129398     /**
129399      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
129400      *
129401      * To disable rounding, configure as **false**.
129402      */
129403     decimalPrecision: 0,
129404
129405     /**
129406      * @cfg {Number} keyIncrement
129407      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
129408      * config is larger, it will be used instead.
129409      */
129410     keyIncrement: 1,
129411
129412     /**
129413      * @cfg {Number} increment
129414      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
129415      */
129416     increment: 0,
129417
129418     /**
129419      * @private
129420      * @property {Number[]} clickRange
129421      * 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],
129422      * 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'
129423      * 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
129424      */
129425     clickRange: [5,15],
129426
129427     /**
129428      * @cfg {Boolean} clickToChange
129429      * Determines whether or not clicking on the Slider axis will change the slider.
129430      */
129431     clickToChange : true,
129432
129433     /**
129434      * @cfg {Boolean} animate
129435      * Turn on or off animation.
129436      */
129437     animate: true,
129438
129439     /**
129440      * @property {Boolean} dragging
129441      * True while the thumb is in a drag operation
129442      */
129443     dragging: false,
129444
129445     /**
129446      * @cfg {Boolean} constrainThumbs
129447      * True to disallow thumbs from overlapping one another.
129448      */
129449     constrainThumbs: true,
129450
129451     componentLayout: 'sliderfield',
129452
129453     /**
129454      * @cfg {Boolean} useTips
129455      * True to use an Ext.slider.Tip to display tips for the value.
129456      */
129457     useTips : true,
129458
129459     /**
129460      * @cfg {Function} tipText
129461      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
129462      * plugin.
129463      */
129464     tipText : null,
129465
129466     ariaRole: 'slider',
129467
129468     // private override
129469     initValue: function() {
129470         var me = this,
129471             extValue = Ext.value,
129472             // Fallback for initial values: values config -> value config -> minValue config -> 0
129473             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
129474             i = 0,
129475             len = values.length;
129476
129477         // Store for use in dirty check
129478         me.originalValue = values;
129479
129480         // Add a thumb for each value
129481         for (; i < len; i++) {
129482             me.addThumb(values[i]);
129483         }
129484     },
129485
129486     // private override
129487     initComponent : function() {
129488         var me = this,
129489             tipPlug,
129490             hasTip;
129491
129492         /**
129493          * @property {Array} thumbs
129494          * Array containing references to each thumb
129495          */
129496         me.thumbs = [];
129497
129498         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
129499
129500         me.addEvents(
129501             /**
129502              * @event beforechange
129503              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
129504              * event and prevent the slider from changing.
129505              * @param {Ext.slider.Multi} slider The slider
129506              * @param {Number} newValue The new value which the slider is being changed to.
129507              * @param {Number} oldValue The old value which the slider was previously.
129508              */
129509             'beforechange',
129510
129511             /**
129512              * @event change
129513              * Fires when the slider value is changed.
129514              * @param {Ext.slider.Multi} slider The slider
129515              * @param {Number} newValue The new value which the slider has been changed to.
129516              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129517              */
129518             'change',
129519
129520             /**
129521              * @event changecomplete
129522              * Fires when the slider value is changed by the user and any drag operations have completed.
129523              * @param {Ext.slider.Multi} slider The slider
129524              * @param {Number} newValue The new value which the slider has been changed to.
129525              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129526              */
129527             'changecomplete',
129528
129529             /**
129530              * @event dragstart
129531              * Fires after a drag operation has started.
129532              * @param {Ext.slider.Multi} slider The slider
129533              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129534              */
129535             'dragstart',
129536
129537             /**
129538              * @event drag
129539              * Fires continuously during the drag operation while the mouse is moving.
129540              * @param {Ext.slider.Multi} slider The slider
129541              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129542              */
129543             'drag',
129544
129545             /**
129546              * @event dragend
129547              * Fires after the drag operation has completed.
129548              * @param {Ext.slider.Multi} slider The slider
129549              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129550              */
129551             'dragend'
129552         );
129553
129554         if (me.vertical) {
129555             Ext.apply(me, Ext.slider.Multi.Vertical);
129556         }
129557
129558         me.callParent();
129559
129560         // only can use it if it exists.
129561         if (me.useTips) {
129562             tipPlug = me.tipText ? {getText: me.tipText} : {};
129563             me.plugins = me.plugins || [];
129564             Ext.each(me.plugins, function(plug){
129565                 if (plug.isSliderTip) {
129566                     hasTip = true;
129567                     return false;
129568                 }
129569             });
129570             if (!hasTip) {
129571                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
129572             }
129573         }
129574     },
129575
129576     /**
129577      * Creates a new thumb and adds it to the slider
129578      * @param {Number} value The initial value to set on the thumb. Defaults to 0
129579      * @return {Ext.slider.Thumb} The thumb
129580      */
129581     addThumb: function(value) {
129582         var me = this,
129583             thumb = Ext.create('Ext.slider.Thumb', {
129584             value    : value,
129585             slider   : me,
129586             index    : me.thumbs.length,
129587             constrain: me.constrainThumbs
129588         });
129589         me.thumbs.push(thumb);
129590
129591         //render the thumb now if needed
129592         if (me.rendered) {
129593             thumb.render();
129594         }
129595
129596         return thumb;
129597     },
129598
129599     /**
129600      * @private
129601      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
129602      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
129603      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
129604      * range, which can result in the user not being able to move any of them.
129605      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
129606      */
129607     promoteThumb: function(topThumb) {
129608         var thumbs = this.thumbs,
129609             ln = thumbs.length,
129610             zIndex, thumb, i;
129611
129612         for (i = 0; i < ln; i++) {
129613             thumb = thumbs[i];
129614
129615             if (thumb == topThumb) {
129616                 thumb.bringToFront();
129617             } else {
129618                 thumb.sendToBack();
129619             }
129620         }
129621     },
129622
129623     // private override
129624     onRender : function() {
129625         var me = this,
129626             i = 0,
129627             thumbs = me.thumbs,
129628             len = thumbs.length,
129629             thumb;
129630
129631         Ext.applyIf(me.subTplData, {
129632             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
129633             minValue: me.minValue,
129634             maxValue: me.maxValue,
129635             value: me.value
129636         });
129637
129638         me.addChildEls('endEl', 'innerEl', 'focusEl');
129639
129640         me.callParent(arguments);
129641
129642         //render each thumb
129643         for (; i < len; i++) {
129644             thumbs[i].render();
129645         }
129646
129647         //calculate the size of half a thumb
129648         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
129649         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
129650
129651     },
129652
129653     /**
129654      * Utility method to set the value of the field when the slider changes.
129655      * @param {Object} slider The slider object.
129656      * @param {Object} v The new value.
129657      * @private
129658      */
129659     onChange : function(slider, v) {
129660         this.setValue(v, undefined, true);
129661     },
129662
129663     /**
129664      * @private
129665      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
129666      */
129667     initEvents : function() {
129668         var me = this;
129669
129670         me.mon(me.el, {
129671             scope    : me,
129672             mousedown: me.onMouseDown,
129673             keydown  : me.onKeyDown,
129674             change : me.onChange
129675         });
129676
129677         me.focusEl.swallowEvent("click", true);
129678     },
129679
129680     /**
129681      * @private
129682      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
129683      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
129684      * @param {Ext.EventObject} e The click event
129685      */
129686     onMouseDown : function(e) {
129687         var me = this,
129688             thumbClicked = false,
129689             i = 0,
129690             thumbs = me.thumbs,
129691             len = thumbs.length,
129692             local;
129693
129694         if (me.disabled) {
129695             return;
129696         }
129697
129698         //see if the click was on any of the thumbs
129699         for (; i < len; i++) {
129700             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
129701         }
129702
129703         if (me.clickToChange && !thumbClicked) {
129704             local = me.innerEl.translatePoints(e.getXY());
129705             me.onClickChange(local);
129706         }
129707         me.focus();
129708     },
129709
129710     /**
129711      * @private
129712      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
129713      * Only changes the value if the click was within this.clickRange.
129714      * @param {Object} local Object containing top and left values for the click event.
129715      */
129716     onClickChange : function(local) {
129717         var me = this,
129718             thumb, index;
129719
129720         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
129721             //find the nearest thumb to the click event
129722             thumb = me.getNearest(local, 'left');
129723             if (!thumb.disabled) {
129724                 index = thumb.index;
129725                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
129726             }
129727         }
129728     },
129729
129730     /**
129731      * @private
129732      * Returns the nearest thumb to a click event, along with its distance
129733      * @param {Object} local Object containing top and left values from a click event
129734      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
129735      * @return {Object} The closest thumb object and its distance from the click event
129736      */
129737     getNearest: function(local, prop) {
129738         var me = this,
129739             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
129740             clickValue = me.reverseValue(localValue),
129741             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
129742             index = 0,
129743             nearest = null,
129744             thumbs = me.thumbs,
129745             i = 0,
129746             len = thumbs.length,
129747             thumb,
129748             value,
129749             dist;
129750
129751         for (; i < len; i++) {
129752             thumb = me.thumbs[i];
129753             value = thumb.value;
129754             dist  = Math.abs(value - clickValue);
129755
129756             if (Math.abs(dist <= nearestDistance)) {
129757                 nearest = thumb;
129758                 index = i;
129759                 nearestDistance = dist;
129760             }
129761         }
129762         return nearest;
129763     },
129764
129765     /**
129766      * @private
129767      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
129768      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
129769      * @param {Ext.EventObject} e The Event object
129770      */
129771     onKeyDown : function(e) {
129772         /*
129773          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
129774          * There's no real sane default for it, so leave it like this until we come up
129775          * with a better way of doing it.
129776          */
129777         var me = this,
129778             k,
129779             val;
129780
129781         if(me.disabled || me.thumbs.length !== 1) {
129782             e.preventDefault();
129783             return;
129784         }
129785         k = e.getKey();
129786
129787         switch(k) {
129788             case e.UP:
129789             case e.RIGHT:
129790                 e.stopEvent();
129791                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
129792                 me.setValue(0, val, undefined, true);
129793             break;
129794             case e.DOWN:
129795             case e.LEFT:
129796                 e.stopEvent();
129797                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
129798                 me.setValue(0, val, undefined, true);
129799             break;
129800             default:
129801                 e.preventDefault();
129802         }
129803     },
129804
129805     // private
129806     afterRender : function() {
129807         var me = this,
129808             i = 0,
129809             thumbs = me.thumbs,
129810             len = thumbs.length,
129811             thumb,
129812             v;
129813
129814         me.callParent(arguments);
129815
129816         for (; i < len; i++) {
129817             thumb = thumbs[i];
129818
129819             if (thumb.value !== undefined) {
129820                 v = me.normalizeValue(thumb.value);
129821                 if (v !== thumb.value) {
129822                     // delete this.value;
129823                     me.setValue(i, v, false);
129824                 } else {
129825                     thumb.move(me.translateValue(v), false);
129826                 }
129827             }
129828         }
129829     },
129830
129831     /**
129832      * @private
129833      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
129834      * the ratio is 2
129835      * @return {Number} The ratio of pixels to mapped values
129836      */
129837     getRatio : function() {
129838         var w = this.innerEl.getWidth(),
129839             v = this.maxValue - this.minValue;
129840         return v === 0 ? w : (w/v);
129841     },
129842
129843     /**
129844      * @private
129845      * Returns a snapped, constrained value when given a desired value
129846      * @param {Number} value Raw number value
129847      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
129848      */
129849     normalizeValue : function(v) {
129850         var me = this;
129851
129852         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
129853         v = Ext.util.Format.round(v, me.decimalPrecision);
129854         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
129855         return v;
129856     },
129857
129858     /**
129859      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
129860      * value will be changed.
129861      * @param {Number} val The new minimum value
129862      */
129863     setMinValue : function(val) {
129864         var me = this,
129865             i = 0,
129866             thumbs = me.thumbs,
129867             len = thumbs.length,
129868             t;
129869
129870         me.minValue = val;
129871         if (me.rendered) {
129872             me.inputEl.dom.setAttribute('aria-valuemin', val);
129873         }
129874
129875         for (; i < len; ++i) {
129876             t = thumbs[i];
129877             t.value = t.value < val ? val : t.value;
129878         }
129879         me.syncThumbs();
129880     },
129881
129882     /**
129883      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
129884      * value will be changed.
129885      * @param {Number} val The new maximum value
129886      */
129887     setMaxValue : function(val) {
129888         var me = this,
129889             i = 0,
129890             thumbs = me.thumbs,
129891             len = thumbs.length,
129892             t;
129893
129894         me.maxValue = val;
129895         if (me.rendered) {
129896             me.inputEl.dom.setAttribute('aria-valuemax', val);
129897         }
129898
129899         for (; i < len; ++i) {
129900             t = thumbs[i];
129901             t.value = t.value > val ? val : t.value;
129902         }
129903         me.syncThumbs();
129904     },
129905
129906     /**
129907      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
129908      * maxValue.
129909      * @param {Number} index Index of the thumb to move
129910      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
129911      * @param {Boolean} [animate=true] Turn on or off animation
129912      */
129913     setValue : function(index, value, animate, changeComplete) {
129914         var me = this,
129915             thumb = me.thumbs[index];
129916
129917         // ensures value is contstrained and snapped
129918         value = me.normalizeValue(value);
129919
129920         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
129921             thumb.value = value;
129922             if (me.rendered) {
129923                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
129924                 // Perhaps this should go on each thumb element rather than the outer element.
129925                 me.inputEl.set({
129926                     'aria-valuenow': value,
129927                     'aria-valuetext': value
129928                 });
129929
129930                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
129931
129932                 me.fireEvent('change', me, value, thumb);
129933                 if (changeComplete) {
129934                     me.fireEvent('changecomplete', me, value, thumb);
129935                 }
129936             }
129937         }
129938     },
129939
129940     /**
129941      * @private
129942      */
129943     translateValue : function(v) {
129944         var ratio = this.getRatio();
129945         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
129946     },
129947
129948     /**
129949      * @private
129950      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
129951      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
129952      * returns 200
129953      * @param {Number} pos The position along the slider to return a mapped value for
129954      * @return {Number} The mapped value for the given position
129955      */
129956     reverseValue : function(pos) {
129957         var ratio = this.getRatio();
129958         return (pos + (this.minValue * ratio)) / ratio;
129959     },
129960
129961     // private
129962     focus : function() {
129963         this.focusEl.focus(10);
129964     },
129965
129966     //private
129967     onDisable: function() {
129968         var me = this,
129969             i = 0,
129970             thumbs = me.thumbs,
129971             len = thumbs.length,
129972             thumb,
129973             el,
129974             xy;
129975
129976         me.callParent();
129977
129978         for (; i < len; i++) {
129979             thumb = thumbs[i];
129980             el = thumb.el;
129981
129982             thumb.disable();
129983
129984             if(Ext.isIE) {
129985                 //IE breaks when using overflow visible and opacity other than 1.
129986                 //Create a place holder for the thumb and display it.
129987                 xy = el.getXY();
129988                 el.hide();
129989
129990                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
129991
129992                 if (!me.thumbHolder) {
129993                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
129994                 }
129995
129996                 me.thumbHolder.show().setXY(xy);
129997             }
129998         }
129999     },
130000
130001     //private
130002     onEnable: function() {
130003         var me = this,
130004             i = 0,
130005             thumbs = me.thumbs,
130006             len = thumbs.length,
130007             thumb,
130008             el;
130009
130010         this.callParent();
130011
130012         for (; i < len; i++) {
130013             thumb = thumbs[i];
130014             el = thumb.el;
130015
130016             thumb.enable();
130017
130018             if (Ext.isIE) {
130019                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
130020
130021                 if (me.thumbHolder) {
130022                     me.thumbHolder.hide();
130023                 }
130024
130025                 el.show();
130026                 me.syncThumbs();
130027             }
130028         }
130029     },
130030
130031     /**
130032      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
130033      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
130034      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
130035      */
130036     syncThumbs : function() {
130037         if (this.rendered) {
130038             var thumbs = this.thumbs,
130039                 length = thumbs.length,
130040                 i = 0;
130041
130042             for (; i < length; i++) {
130043                 thumbs[i].move(this.translateValue(thumbs[i].value));
130044             }
130045         }
130046     },
130047
130048     /**
130049      * Returns the current value of the slider
130050      * @param {Number} index The index of the thumb to return a value for
130051      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
130052      * no index is given.
130053      */
130054     getValue : function(index) {
130055         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
130056     },
130057
130058     /**
130059      * Returns an array of values - one for the location of each thumb
130060      * @return {Number[]} The set of thumb values
130061      */
130062     getValues: function() {
130063         var values = [],
130064             i = 0,
130065             thumbs = this.thumbs,
130066             len = thumbs.length;
130067
130068         for (; i < len; i++) {
130069             values.push(thumbs[i].value);
130070         }
130071
130072         return values;
130073     },
130074
130075     getSubmitValue: function() {
130076         var me = this;
130077         return (me.disabled || !me.submitValue) ? null : me.getValue();
130078     },
130079
130080     reset: function() {
130081         var me = this,
130082             Array = Ext.Array;
130083         Array.forEach(Array.from(me.originalValue), function(val, i) {
130084             me.setValue(i, val);
130085         });
130086         me.clearInvalid();
130087         // delete here so we reset back to the original state
130088         delete me.wasValid;
130089     },
130090
130091     // private
130092     beforeDestroy : function() {
130093         var me = this;
130094
130095         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
130096         Ext.each(me.thumbs, function(thumb) {
130097             Ext.destroy(thumb);
130098         }, me);
130099
130100         me.callParent();
130101     },
130102
130103     statics: {
130104         // Method overrides to support slider with vertical orientation
130105         Vertical: {
130106             getRatio : function() {
130107                 var h = this.innerEl.getHeight(),
130108                     v = this.maxValue - this.minValue;
130109                 return h/v;
130110             },
130111
130112             onClickChange : function(local) {
130113                 var me = this,
130114                     thumb, index, bottom;
130115
130116                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
130117                     thumb = me.getNearest(local, 'top');
130118                     if (!thumb.disabled) {
130119                         index = thumb.index;
130120                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
130121
130122                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
130123                     }
130124                 }
130125             }
130126         }
130127     }
130128 });
130129
130130 /**
130131  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130132  * and animation. Can be added as an item to any container.
130133  *
130134  *     @example
130135  *     Ext.create('Ext.slider.Single', {
130136  *         width: 200,
130137  *         value: 50,
130138  *         increment: 10,
130139  *         minValue: 0,
130140  *         maxValue: 100,
130141  *         renderTo: Ext.getBody()
130142  *     });
130143  *
130144  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
130145  */
130146 Ext.define('Ext.slider.Single', {
130147     extend: 'Ext.slider.Multi',
130148     alias: ['widget.slider', 'widget.sliderfield'],
130149     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
130150
130151     /**
130152      * Returns the current value of the slider
130153      * @return {Number} The current value of the slider
130154      */
130155     getValue: function() {
130156         // just returns the value of the first thumb, which should be the only one in a single slider
130157         return this.callParent([0]);
130158     },
130159
130160     /**
130161      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
130162      * maxValue.
130163      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
130164      * @param {Boolean} [animate] Turn on or off animation
130165      */
130166     setValue: function(value, animate) {
130167         var args = Ext.toArray(arguments),
130168             len  = args.length;
130169
130170         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
130171         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
130172         // signature without the required index. The index will always be 0 for a single slider
130173         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
130174             args.unshift(0);
130175         }
130176
130177         return this.callParent(args);
130178     },
130179
130180     // private
130181     getNearest : function(){
130182         // Since there's only 1 thumb, it's always the nearest
130183         return this.thumbs[0];
130184     }
130185 });
130186
130187 /**
130188  * @author Ed Spencer
130189  * @class Ext.tab.Tab
130190  * @extends Ext.button.Button
130191  *
130192  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
130193  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
130194  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
130195  */
130196 Ext.define('Ext.tab.Tab', {
130197     extend: 'Ext.button.Button',
130198     alias: 'widget.tab',
130199
130200     requires: [
130201         'Ext.layout.component.Tab',
130202         'Ext.util.KeyNav'
130203     ],
130204
130205     componentLayout: 'tab',
130206
130207     isTab: true,
130208
130209     baseCls: Ext.baseCSSPrefix + 'tab',
130210
130211     /**
130212      * @cfg {String} activeCls
130213      * The CSS class to be applied to a Tab when it is active.
130214      * Providing your own CSS for this class enables you to customize the active state.
130215      */
130216     activeCls: 'active',
130217
130218     /**
130219      * @cfg {String} disabledCls
130220      * The CSS class to be applied to a Tab when it is disabled.
130221      */
130222
130223     /**
130224      * @cfg {String} closableCls
130225      * The CSS class which is added to the tab when it is closable
130226      */
130227     closableCls: 'closable',
130228
130229     /**
130230      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
130231      */
130232     closable: true,
130233
130234     /**
130235      * @cfg {String} closeText
130236      * The accessible text label for the close button link; only used when {@link #closable} = true.
130237      */
130238     closeText: 'Close Tab',
130239
130240     /**
130241      * @property {Boolean} active
130242      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
130243      */
130244     active: false,
130245
130246     /**
130247      * @property closable
130248      * @type Boolean
130249      * True if the tab is currently closable
130250      */
130251
130252     scale: false,
130253
130254     position: 'top',
130255
130256     initComponent: function() {
130257         var me = this;
130258
130259         me.addEvents(
130260             /**
130261              * @event activate
130262              * Fired when the tab is activated.
130263              * @param {Ext.tab.Tab} this
130264              */
130265             'activate',
130266
130267             /**
130268              * @event deactivate
130269              * Fired when the tab is deactivated.
130270              * @param {Ext.tab.Tab} this
130271              */
130272             'deactivate',
130273
130274             /**
130275              * @event beforeclose
130276              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
130277              * false from any listener to stop the close event being fired
130278              * @param {Ext.tab.Tab} tab The Tab object
130279              */
130280             'beforeclose',
130281
130282             /**
130283              * @event close
130284              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
130285              * @param {Ext.tab.Tab} tab The Tab object
130286              */
130287             'close'
130288         );
130289
130290         me.callParent(arguments);
130291
130292         if (me.card) {
130293             me.setCard(me.card);
130294         }
130295     },
130296
130297     /**
130298      * @ignore
130299      */
130300     onRender: function() {
130301         var me = this,
130302             tabBar = me.up('tabbar'),
130303             tabPanel = me.up('tabpanel');
130304
130305         me.addClsWithUI(me.position);
130306
130307         // Set all the state classNames, as they need to include the UI
130308         // me.disabledCls = me.getClsWithUIs('disabled');
130309
130310         me.syncClosableUI();
130311
130312         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
130313         if (!me.minWidth) {
130314             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
130315             if (!me.minWidth && tabPanel) {
130316                 me.minWidth = tabPanel.minTabWidth;
130317             }
130318             if (me.minWidth && me.iconCls) {
130319                 me.minWidth += 25;
130320             }
130321         }
130322         if (!me.maxWidth) {
130323             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
130324             if (!me.maxWidth && tabPanel) {
130325                 me.maxWidth = tabPanel.maxTabWidth;
130326             }
130327         }
130328
130329         me.callParent(arguments);
130330
130331         if (me.active) {
130332             me.activate(true);
130333         }
130334
130335         me.syncClosableElements();
130336
130337         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
130338             enter: me.onEnterKey,
130339             del: me.onDeleteKey,
130340             scope: me
130341         });
130342     },
130343
130344     // inherit docs
130345     enable : function(silent) {
130346         var me = this;
130347
130348         me.callParent(arguments);
130349
130350         me.removeClsWithUI(me.position + '-disabled');
130351
130352         return me;
130353     },
130354
130355     // inherit docs
130356     disable : function(silent) {
130357         var me = this;
130358
130359         me.callParent(arguments);
130360
130361         me.addClsWithUI(me.position + '-disabled');
130362
130363         return me;
130364     },
130365
130366     /**
130367      * @ignore
130368      */
130369     onDestroy: function() {
130370         var me = this;
130371
130372         if (me.closeEl) {
130373             me.closeEl.un('click', Ext.EventManager.preventDefault);
130374             me.closeEl = null;
130375         }
130376
130377         Ext.destroy(me.keyNav);
130378         delete me.keyNav;
130379
130380         me.callParent(arguments);
130381     },
130382
130383     /**
130384      * Sets the tab as either closable or not
130385      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
130386      * close button will appear on the tab)
130387      */
130388     setClosable: function(closable) {
130389         var me = this;
130390
130391         // Closable must be true if no args
130392         closable = (!arguments.length || !!closable);
130393
130394         if (me.closable != closable) {
130395             me.closable = closable;
130396
130397             // set property on the user-facing item ('card'):
130398             if (me.card) {
130399                 me.card.closable = closable;
130400             }
130401
130402             me.syncClosableUI();
130403
130404             if (me.rendered) {
130405                 me.syncClosableElements();
130406
130407                 // Tab will change width to accommodate close icon
130408                 me.doComponentLayout();
130409                 if (me.ownerCt) {
130410                     me.ownerCt.doLayout();
130411                 }
130412             }
130413         }
130414     },
130415
130416     /**
130417      * This method ensures that the closeBtn element exists or not based on 'closable'.
130418      * @private
130419      */
130420     syncClosableElements: function () {
130421         var me = this;
130422
130423         if (me.closable) {
130424             if (!me.closeEl) {
130425                 me.closeEl = me.el.createChild({
130426                     tag: 'a',
130427                     cls: me.baseCls + '-close-btn',
130428                     href: '#',
130429                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
130430                     title: me.closeText
130431                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
130432             }
130433         } else {
130434             var closeEl = me.closeEl;
130435             if (closeEl) {
130436                 closeEl.un('click', Ext.EventManager.preventDefault);
130437                 closeEl.remove();
130438                 me.closeEl = null;
130439             }
130440         }
130441     },
130442
130443     /**
130444      * This method ensures that the UI classes are added or removed based on 'closable'.
130445      * @private
130446      */
130447     syncClosableUI: function () {
130448         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
130449
130450         if (me.closable) {
130451             me.addClsWithUI(classes);
130452         } else {
130453             me.removeClsWithUI(classes);
130454         }
130455     },
130456
130457     /**
130458      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
130459      * belongs to and would not need to be done by the developer
130460      * @param {Ext.Component} card The card to set
130461      */
130462     setCard: function(card) {
130463         var me = this;
130464
130465         me.card = card;
130466         me.setText(me.title || card.title);
130467         me.setIconCls(me.iconCls || card.iconCls);
130468     },
130469
130470     /**
130471      * @private
130472      * Listener attached to click events on the Tab's close button
130473      */
130474     onCloseClick: function() {
130475         var me = this;
130476
130477         if (me.fireEvent('beforeclose', me) !== false) {
130478             if (me.tabBar) {
130479                 if (me.tabBar.closeTab(me) === false) {
130480                     // beforeclose on the panel vetoed the event, stop here
130481                     return;
130482                 }
130483             } else {
130484                 // if there's no tabbar, fire the close event
130485                 me.fireEvent('close', me);
130486             }
130487         }
130488     },
130489
130490     /**
130491      * Fires the close event on the tab.
130492      * @private
130493      */
130494     fireClose: function(){
130495         this.fireEvent('close', this);
130496     },
130497
130498     /**
130499      * @private
130500      */
130501     onEnterKey: function(e) {
130502         var me = this;
130503
130504         if (me.tabBar) {
130505             me.tabBar.onClick(e, me.el);
130506         }
130507     },
130508
130509    /**
130510      * @private
130511      */
130512     onDeleteKey: function(e) {
130513         var me = this;
130514
130515         if (me.closable) {
130516             me.onCloseClick();
130517         }
130518     },
130519
130520     // @private
130521     activate : function(supressEvent) {
130522         var me = this;
130523
130524         me.active = true;
130525         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130526
130527         if (supressEvent !== true) {
130528             me.fireEvent('activate', me);
130529         }
130530     },
130531
130532     // @private
130533     deactivate : function(supressEvent) {
130534         var me = this;
130535
130536         me.active = false;
130537         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130538
130539         if (supressEvent !== true) {
130540             me.fireEvent('deactivate', me);
130541         }
130542     }
130543 });
130544
130545 /**
130546  * @author Ed Spencer
130547  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
130548  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
130549  */
130550 Ext.define('Ext.tab.Bar', {
130551     extend: 'Ext.panel.Header',
130552     alias: 'widget.tabbar',
130553     baseCls: Ext.baseCSSPrefix + 'tab-bar',
130554
130555     requires: [
130556         'Ext.tab.Tab',
130557         'Ext.FocusManager'
130558     ],
130559
130560     isTabBar: true,
130561     
130562     /**
130563      * @cfg {String} title @hide
130564      */
130565     
130566     /**
130567      * @cfg {String} iconCls @hide
130568      */
130569
130570     // @private
130571     defaultType: 'tab',
130572
130573     /**
130574      * @cfg {Boolean} plain
130575      * True to not show the full background on the tabbar
130576      */
130577     plain: false,
130578
130579     // @private
130580     renderTpl: [
130581         '<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>',
130582         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
130583     ],
130584
130585     /**
130586      * @cfg {Number} minTabWidth
130587      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
130588      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
130589      */
130590
130591     /**
130592      * @cfg {Number} maxTabWidth
130593      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
130594      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
130595      */
130596
130597     // @private
130598     initComponent: function() {
130599         var me = this,
130600             keys;
130601
130602         if (me.plain) {
130603             me.setUI(me.ui + '-plain');
130604         }
130605
130606         me.addClsWithUI(me.dock);
130607
130608         me.addEvents(
130609             /**
130610              * @event change
130611              * Fired when the currently-active tab has changed
130612              * @param {Ext.tab.Bar} tabBar The TabBar
130613              * @param {Ext.tab.Tab} tab The new Tab
130614              * @param {Ext.Component} card The card that was just shown in the TabPanel
130615              */
130616             'change'
130617         );
130618
130619         me.addChildEls('body', 'strip');
130620         me.callParent(arguments);
130621
130622         // TabBar must override the Header's align setting.
130623         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
130624         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
130625
130626         me.remove(me.titleCmp);
130627         delete me.titleCmp;
130628
130629         // Subscribe to Ext.FocusManager for key navigation
130630         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
130631         Ext.FocusManager.subscribe(me, {
130632             keys: keys
130633         });
130634
130635         Ext.apply(me.renderData, {
130636             bodyCls: me.bodyCls
130637         });
130638     },
130639
130640     // @private
130641     onAdd: function(tab) {
130642         tab.position = this.dock;
130643         this.callParent(arguments);
130644     },
130645     
130646     onRemove: function(tab) {
130647         var me = this;
130648         
130649         if (tab === me.previousTab) {
130650             me.previousTab = null;
130651         }
130652         if (me.items.getCount() === 0) {
130653             me.activeTab = null;
130654         }
130655         me.callParent(arguments);    
130656     },
130657
130658     // @private
130659     afterRender: function() {
130660         var me = this;
130661
130662         me.mon(me.el, {
130663             scope: me,
130664             click: me.onClick,
130665             delegate: '.' + Ext.baseCSSPrefix + 'tab'
130666         });
130667         me.callParent(arguments);
130668
130669     },
130670
130671     afterComponentLayout : function() {
130672         var me = this;
130673
130674         me.callParent(arguments);
130675         me.strip.setWidth(me.el.getWidth());
130676     },
130677
130678     // @private
130679     onClick: function(e, target) {
130680         // The target might not be a valid tab el.
130681         var tab = Ext.getCmp(target.id),
130682             tabPanel = this.tabPanel;
130683
130684         target = e.getTarget();
130685
130686         if (tab && tab.isDisabled && !tab.isDisabled()) {
130687             if (tab.closable && target === tab.closeEl.dom) {
130688                 tab.onCloseClick();
130689             } else {
130690                 if (tabPanel) {
130691                     // TabPanel will card setActiveTab of the TabBar
130692                     tabPanel.setActiveTab(tab.card);
130693                 } else {
130694                     this.setActiveTab(tab);
130695                 }
130696                 tab.focus();
130697             }
130698         }
130699     },
130700
130701     /**
130702      * @private
130703      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
130704      * @param {Ext.tab.Tab} tab The tab to close
130705      */
130706     closeTab: function(tab) {
130707         var me = this,
130708             card = tab.card,
130709             tabPanel = me.tabPanel,
130710             nextTab;
130711
130712         if (card && card.fireEvent('beforeclose', card) === false) {
130713             return false;
130714         }
130715
130716         if (tab.active && me.items.getCount() > 1) {
130717             nextTab = me.previousTab || tab.next('tab') || me.items.first();
130718             me.setActiveTab(nextTab);
130719             if (tabPanel) {
130720                 tabPanel.setActiveTab(nextTab.card);
130721             }
130722         }
130723         /*
130724          * force the close event to fire. By the time this function returns,
130725          * the tab is already destroyed and all listeners have been purged
130726          * so the tab can't fire itself.
130727          */
130728         tab.fireClose();
130729         me.remove(tab);
130730
130731         if (tabPanel && card) {
130732             card.fireEvent('close', card);
130733             tabPanel.remove(card);
130734         }
130735
130736         if (nextTab) {
130737             nextTab.focus();
130738         }
130739     },
130740
130741     /**
130742      * @private
130743      * Marks the given tab as active
130744      * @param {Ext.tab.Tab} tab The tab to mark active
130745      */
130746     setActiveTab: function(tab) {
130747         if (tab.disabled) {
130748             return;
130749         }
130750         var me = this;
130751         if (me.activeTab) {
130752             me.previousTab = me.activeTab;
130753             me.activeTab.deactivate();
130754         }
130755         tab.activate();
130756
130757         if (me.rendered) {
130758             me.layout.layout();
130759             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
130760         }
130761         me.activeTab = tab;
130762         me.fireEvent('change', me, tab, tab.card);
130763     }
130764 });
130765
130766 /**
130767  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
130768  *
130769  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
130770  * layout purposes, but also have special support for containing child Components
130771  * (`{@link Ext.container.Container#items items}`) that are managed using a
130772  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
130773  *
130774  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
130775  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
130776  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
130777  *
130778  * ## TabPanel's layout
130779  *
130780  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
130781  * Panels added to the TabPanel will have their header hidden by default because the Tab will
130782  * automatically take the Panel's configured title and icon.
130783  *
130784  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
130785  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
130786  * This means that a TabPanel will not display any configured title, and will not display any configured
130787  * header {@link Ext.panel.Panel#tools tools}.
130788  *
130789  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
130790  * `{@link Ext.container.Container#layout layout: 'fit'}`.
130791  *
130792  * ## Controlling tabs
130793  *
130794  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
130795  * by specifying the tabConfig option:
130796  *
130797  *     @example
130798  *     Ext.create('Ext.tab.Panel', {
130799  *         width: 400,
130800  *         height: 400,
130801  *         renderTo: document.body,
130802  *         items: [{
130803  *             title: 'Foo'
130804  *         }, {
130805  *             title: 'Bar',
130806  *             tabConfig: {
130807  *                 title: 'Custom Title',
130808  *                 tooltip: 'A button tooltip'
130809  *             }
130810  *         }]
130811  *     });
130812  *
130813  * # Examples
130814  *
130815  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
130816  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
130817  * active by default.
130818  *
130819  *     @example
130820  *     Ext.create('Ext.tab.Panel', {
130821  *         width: 300,
130822  *         height: 200,
130823  *         activeTab: 0,
130824  *         items: [
130825  *             {
130826  *                 title: 'Tab 1',
130827  *                 bodyPadding: 10,
130828  *                 html : 'A simple tab'
130829  *             },
130830  *             {
130831  *                 title: 'Tab 2',
130832  *                 html : 'Another one'
130833  *             }
130834  *         ],
130835  *         renderTo : Ext.getBody()
130836  *     });
130837  *
130838  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
130839  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
130840  * tab property on the child item.
130841  *
130842  *     @example
130843  *     var tabs = Ext.create('Ext.tab.Panel', {
130844  *         width: 400,
130845  *         height: 400,
130846  *         renderTo: document.body,
130847  *         items: [{
130848  *             title: 'Home',
130849  *             html: 'Home',
130850  *             itemId: 'home'
130851  *         }, {
130852  *             title: 'Users',
130853  *             html: 'Users',
130854  *             itemId: 'users',
130855  *             hidden: true
130856  *         }, {
130857  *             title: 'Tickets',
130858  *             html: 'Tickets',
130859  *             itemId: 'tickets'
130860  *         }]
130861  *     });
130862  *
130863  *     setTimeout(function(){
130864  *         tabs.child('#home').tab.hide();
130865  *         var users = tabs.child('#users');
130866  *         users.tab.show();
130867  *         tabs.setActiveTab(users);
130868  *     }, 1000);
130869  *
130870  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
130871  *
130872  *     @example
130873  *     Ext.create('Ext.tab.Panel', {
130874  *         width: 300,
130875  *         height: 200,
130876  *         activeTab: 0,
130877  *         plain: true,
130878  *         items: [
130879  *             {
130880  *                 title: 'Tab 1',
130881  *                 bodyPadding: 10,
130882  *                 html : 'A simple tab'
130883  *             },
130884  *             {
130885  *                 title: 'Tab 2',
130886  *                 html : 'Another one'
130887  *             }
130888  *         ],
130889  *         renderTo : Ext.getBody()
130890  *     });
130891  *
130892  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
130893  * position where the tabs are displayed. The available options for this are `'top'` (default) and
130894  * `'bottom'`.
130895  *
130896  *     @example
130897  *     Ext.create('Ext.tab.Panel', {
130898  *         width: 300,
130899  *         height: 200,
130900  *         activeTab: 0,
130901  *         bodyPadding: 10,
130902  *         tabPosition: 'bottom',
130903  *         items: [
130904  *             {
130905  *                 title: 'Tab 1',
130906  *                 html : 'A simple tab'
130907  *             },
130908  *             {
130909  *                 title: 'Tab 2',
130910  *                 html : 'Another one'
130911  *             }
130912  *         ],
130913  *         renderTo : Ext.getBody()
130914  *     });
130915  *
130916  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
130917  * current active tab. You can either give it an index or an instance of a tab. For example:
130918  *
130919  *     @example
130920  *     var tabs = Ext.create('Ext.tab.Panel', {
130921  *         items: [
130922  *             {
130923  *                 id   : 'my-tab',
130924  *                 title: 'Tab 1',
130925  *                 html : 'A simple tab'
130926  *             },
130927  *             {
130928  *                 title: 'Tab 2',
130929  *                 html : 'Another one'
130930  *             }
130931  *         ],
130932  *         renderTo : Ext.getBody()
130933  *     });
130934  *
130935  *     var tab = Ext.getCmp('my-tab');
130936  *
130937  *     Ext.create('Ext.button.Button', {
130938  *         renderTo: Ext.getBody(),
130939  *         text    : 'Select the first tab',
130940  *         scope   : this,
130941  *         handler : function() {
130942  *             tabs.setActiveTab(tab);
130943  *         }
130944  *     });
130945  *
130946  *     Ext.create('Ext.button.Button', {
130947  *         text    : 'Select the second tab',
130948  *         scope   : this,
130949  *         handler : function() {
130950  *             tabs.setActiveTab(1);
130951  *         },
130952  *         renderTo : Ext.getBody()
130953  *     });
130954  *
130955  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
130956  *
130957  *     @example
130958  *     var tabs = Ext.create('Ext.tab.Panel', {
130959  *         items: [
130960  *             {
130961  *                 title: 'Tab 1',
130962  *                 html : 'A simple tab'
130963  *             },
130964  *             {
130965  *                 title: 'Tab 2',
130966  *                 html : 'Another one'
130967  *             }
130968  *         ],
130969  *         renderTo : Ext.getBody()
130970  *     });
130971  *
130972  *     Ext.create('Ext.button.Button', {
130973  *         text    : 'Get active tab',
130974  *         scope   : this,
130975  *         handler : function() {
130976  *             var tab = tabs.getActiveTab();
130977  *             alert('Current tab: ' + tab.title);
130978  *         },
130979  *         renderTo : Ext.getBody()
130980  *     });
130981  *
130982  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
130983  * object for a panel.
130984  *
130985  *     @example
130986  *     var tabs = Ext.create('Ext.tab.Panel', {
130987  *         items: [
130988  *             {
130989  *                 title: 'Tab 1',
130990  *                 html : 'A simple tab'
130991  *             },
130992  *             {
130993  *                 title: 'Tab 2',
130994  *                 html : 'Another one'
130995  *             }
130996  *         ],
130997  *         renderTo : Ext.getBody()
130998  *     });
130999  *
131000  *     Ext.create('Ext.button.Button', {
131001  *         text    : 'New tab',
131002  *         scope   : this,
131003  *         handler : function() {
131004  *             var tab = tabs.add({
131005  *                 // we use the tabs.items property to get the length of current items/tabs
131006  *                 title: 'Tab ' + (tabs.items.length + 1),
131007  *                 html : 'Another one'
131008  *             });
131009  *
131010  *             tabs.setActiveTab(tab);
131011  *         },
131012  *         renderTo : Ext.getBody()
131013  *     });
131014  *
131015  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
131016  * with an config object for a panel.
131017  *
131018  *     @example
131019  *     var tabs = Ext.create('Ext.tab.Panel', {
131020  *         items: [
131021  *             {
131022  *                 title: 'Tab 1',
131023  *                 html : 'A simple tab'
131024  *             },
131025  *             {
131026  *                 id   : 'remove-this-tab',
131027  *                 title: 'Tab 2',
131028  *                 html : 'Another one'
131029  *             }
131030  *         ],
131031  *         renderTo : Ext.getBody()
131032  *     });
131033  *
131034  *     Ext.create('Ext.button.Button', {
131035  *         text    : 'Remove tab',
131036  *         scope   : this,
131037  *         handler : function() {
131038  *             var tab = Ext.getCmp('remove-this-tab');
131039  *             tabs.remove(tab);
131040  *         },
131041  *         renderTo : Ext.getBody()
131042  *     });
131043  */
131044 Ext.define('Ext.tab.Panel', {
131045     extend: 'Ext.panel.Panel',
131046     alias: 'widget.tabpanel',
131047     alternateClassName: ['Ext.TabPanel'],
131048
131049     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
131050
131051     /**
131052      * @cfg {String} tabPosition
131053      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
131054      */
131055     tabPosition : 'top',
131056
131057     /**
131058      * @cfg {String/Number} activeItem
131059      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
131060      */
131061
131062     /**
131063      * @cfg {String/Number/Ext.Component} activeTab
131064      * The tab to activate initially. Either an ID, index or the tab component itself.
131065      */
131066
131067     /**
131068      * @cfg {Object} tabBar
131069      * Optional configuration object for the internal {@link Ext.tab.Bar}.
131070      * If present, this is passed straight through to the TabBar's constructor
131071      */
131072
131073     /**
131074      * @cfg {Object} layout
131075      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
131076      * If present, this is passed straight through to the layout's constructor
131077      */
131078
131079     /**
131080      * @cfg {Boolean} removePanelHeader
131081      * True to instruct each Panel added to the TabContainer to not render its header element.
131082      * This is to ensure that the title of the panel does not appear twice.
131083      */
131084     removePanelHeader: true,
131085
131086     /**
131087      * @cfg {Boolean} plain
131088      * True to not show the full background on the TabBar.
131089      */
131090     plain: false,
131091
131092     /**
131093      * @cfg {String} itemCls
131094      * The class added to each child item of this TabPanel.
131095      */
131096     itemCls: 'x-tabpanel-child',
131097
131098     /**
131099      * @cfg {Number} minTabWidth
131100      * The minimum width for a tab in the {@link #tabBar}.
131101      */
131102     minTabWidth: undefined,
131103
131104     /**
131105      * @cfg {Number} maxTabWidth The maximum width for each tab.
131106      */
131107     maxTabWidth: undefined,
131108
131109     /**
131110      * @cfg {Boolean} deferredRender
131111      *
131112      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
131113      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
131114      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
131115      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
131116      * performance.
131117      *
131118      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
131119      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
131120      *
131121      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
131122      */
131123     deferredRender : true,
131124
131125     //inherit docs
131126     initComponent: function() {
131127         var me = this,
131128             dockedItems = me.dockedItems || [],
131129             activeTab = me.activeTab || 0;
131130
131131         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
131132             owner: me,
131133             deferredRender: me.deferredRender,
131134             itemCls: me.itemCls
131135         }, me.layout));
131136
131137         /**
131138          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
131139          */
131140         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
131141             dock: me.tabPosition,
131142             plain: me.plain,
131143             border: me.border,
131144             cardLayout: me.layout,
131145             tabPanel: me
131146         }));
131147
131148         if (dockedItems && !Ext.isArray(dockedItems)) {
131149             dockedItems = [dockedItems];
131150         }
131151
131152         dockedItems.push(me.tabBar);
131153         me.dockedItems = dockedItems;
131154
131155         me.addEvents(
131156             /**
131157              * @event
131158              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
131159              * the tabchange
131160              * @param {Ext.tab.Panel} tabPanel The TabPanel
131161              * @param {Ext.Component} newCard The card that is about to be activated
131162              * @param {Ext.Component} oldCard The card that is currently active
131163              */
131164             'beforetabchange',
131165
131166             /**
131167              * @event
131168              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
131169              * @param {Ext.tab.Panel} tabPanel The TabPanel
131170              * @param {Ext.Component} newCard The newly activated item
131171              * @param {Ext.Component} oldCard The previously active item
131172              */
131173             'tabchange'
131174         );
131175         me.callParent(arguments);
131176
131177         //set the active tab
131178         me.setActiveTab(activeTab);
131179         //set the active tab after initial layout
131180         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
131181     },
131182
131183     /**
131184      * @private
131185      * We have to wait until after the initial layout to visually activate the activeTab (if set).
131186      * The active tab has different margins than normal tabs, so if the initial layout happens with
131187      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
131188      * until after the initial layout avoids this issue.
131189      */
131190     afterInitialLayout: function() {
131191         var me = this,
131192             card = me.getComponent(me.activeTab);
131193
131194         if (card) {
131195             me.layout.setActiveItem(card);
131196         }
131197     },
131198
131199     /**
131200      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
131201      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
131202      */
131203     setActiveTab: function(card) {
131204         var me = this,
131205             previous;
131206
131207         card = me.getComponent(card);
131208         if (card) {
131209             previous = me.getActiveTab();
131210
131211             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
131212                 return false;
131213             }
131214
131215             me.tabBar.setActiveTab(card.tab);
131216             me.activeTab = card;
131217             if (me.rendered) {
131218                 me.layout.setActiveItem(card);
131219             }
131220
131221             if (previous && previous !== card) {
131222                 me.fireEvent('tabchange', me, card, previous);
131223             }
131224         }
131225     },
131226
131227     /**
131228      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
131229      * child component this will return whatever was configured in the {@link #activeTab} config option
131230      * @return {String/Number/Ext.Component} The currently active item
131231      */
131232     getActiveTab: function() {
131233         return this.activeTab;
131234     },
131235
131236     /**
131237      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
131238      * @return {Ext.tab.Bar} The TabBar
131239      */
131240     getTabBar: function() {
131241         return this.tabBar;
131242     },
131243
131244     /**
131245      * @ignore
131246      * Makes sure we have a Tab for each item added to the TabPanel
131247      */
131248     onAdd: function(item, index) {
131249         var me = this,
131250             cfg = item.tabConfig || {},
131251             defaultConfig = {
131252                 xtype: 'tab',
131253                 card: item,
131254                 disabled: item.disabled,
131255                 closable: item.closable,
131256                 hidden: item.hidden,
131257                 tabBar: me.tabBar
131258             };
131259
131260         if (item.closeText) {
131261             defaultConfig.closeText = item.closeText;
131262         }
131263         cfg = Ext.applyIf(cfg, defaultConfig);
131264         item.tab = me.tabBar.insert(index, cfg);
131265
131266         item.on({
131267             scope : me,
131268             enable: me.onItemEnable,
131269             disable: me.onItemDisable,
131270             beforeshow: me.onItemBeforeShow,
131271             iconchange: me.onItemIconChange,
131272             titlechange: me.onItemTitleChange
131273         });
131274
131275         if (item.isPanel) {
131276             if (me.removePanelHeader) {
131277                 item.preventHeader = true;
131278                 if (item.rendered) {
131279                     item.updateHeader();
131280                 }
131281             }
131282             if (item.isPanel && me.border) {
131283                 item.setBorder(false);
131284             }
131285         }
131286
131287         // ensure that there is at least one active tab
131288         if (this.rendered && me.items.getCount() === 1) {
131289             me.setActiveTab(0);
131290         }
131291     },
131292
131293     /**
131294      * @private
131295      * Enable corresponding tab when item is enabled.
131296      */
131297     onItemEnable: function(item){
131298         item.tab.enable();
131299     },
131300
131301     /**
131302      * @private
131303      * Disable corresponding tab when item is enabled.
131304      */
131305     onItemDisable: function(item){
131306         item.tab.disable();
131307     },
131308
131309     /**
131310      * @private
131311      * Sets activeTab before item is shown.
131312      */
131313     onItemBeforeShow: function(item) {
131314         if (item !== this.activeTab) {
131315             this.setActiveTab(item);
131316             return false;
131317         }
131318     },
131319
131320     /**
131321      * @private
131322      * Update the tab iconCls when panel iconCls has been set or changed.
131323      */
131324     onItemIconChange: function(item, newIconCls) {
131325         item.tab.setIconCls(newIconCls);
131326         this.getTabBar().doLayout();
131327     },
131328
131329     /**
131330      * @private
131331      * Update the tab title when panel title has been set or changed.
131332      */
131333     onItemTitleChange: function(item, newTitle) {
131334         item.tab.setText(newTitle);
131335         this.getTabBar().doLayout();
131336     },
131337
131338
131339     /**
131340      * @ignore
131341      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
131342      * so we can do preprocessing before then to find the card's index
131343      */
131344     doRemove: function(item, autoDestroy) {
131345         var me = this,
131346             items = me.items,
131347             // At this point the item hasn't been removed from the items collection.
131348             // As such, if we want to check if there are no more tabs left, we have to
131349             // check for one, as opposed to 0.
131350             hasItemsLeft = items.getCount() > 1;
131351
131352         if (me.destroying || !hasItemsLeft) {
131353             me.activeTab = null;
131354         } else if (item === me.activeTab) {
131355              me.setActiveTab(item.next() || items.getAt(0));
131356         }
131357         me.callParent(arguments);
131358
131359         // Remove the two references
131360         delete item.tab.card;
131361         delete item.tab;
131362     },
131363
131364     /**
131365      * @ignore
131366      * Makes sure we remove the corresponding Tab when an item is removed
131367      */
131368     onRemove: function(item, autoDestroy) {
131369         var me = this;
131370
131371         item.un({
131372             scope : me,
131373             enable: me.onItemEnable,
131374             disable: me.onItemDisable,
131375             beforeshow: me.onItemBeforeShow
131376         });
131377         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
131378             me.tabBar.remove(item.tab);
131379         }
131380     }
131381 });
131382
131383 /**
131384  * A simple element that adds extra horizontal space between items in a toolbar.
131385  * By default a 2px wide space is added via CSS specification:
131386  *
131387  *     .x-toolbar .x-toolbar-spacer {
131388  *         width: 2px;
131389  *     }
131390  *
131391  * Example:
131392  *
131393  *     @example
131394  *     Ext.create('Ext.panel.Panel', {
131395  *         title: 'Toolbar Spacer Example',
131396  *         width: 300,
131397  *         height: 200,
131398  *         tbar : [
131399  *             'Item 1',
131400  *             { xtype: 'tbspacer' }, // or ' '
131401  *             'Item 2',
131402  *             // space width is also configurable via javascript
131403  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
131404  *             'Item 3'
131405  *         ],
131406  *         renderTo: Ext.getBody()
131407  *     });
131408  */
131409 Ext.define('Ext.toolbar.Spacer', {
131410     extend: 'Ext.Component',
131411     alias: 'widget.tbspacer',
131412     alternateClassName: 'Ext.Toolbar.Spacer',
131413     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
131414     focusable: false
131415 });
131416 /**
131417  * @class Ext.tree.Column
131418  * @extends Ext.grid.column.Column
131419  * 
131420  * Provides indentation and folder structure markup for a Tree taking into account
131421  * depth and position within the tree hierarchy.
131422  * 
131423  * @private
131424  */
131425 Ext.define('Ext.tree.Column', {
131426     extend: 'Ext.grid.column.Column',
131427     alias: 'widget.treecolumn',
131428
131429     initComponent: function() {
131430         var origRenderer = this.renderer || this.defaultRenderer,
131431             origScope    = this.scope || window;
131432
131433         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
131434             var buf   = [],
131435                 format = Ext.String.format,
131436                 depth = record.getDepth(),
131437                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
131438                 elbowPrefix = treePrefix + 'elbow-',
131439                 expanderCls = treePrefix + 'expander',
131440                 imgText     = '<img src="{1}" class="{0}" />',
131441                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
131442                 formattedValue = origRenderer.apply(origScope, arguments),
131443                 href = record.get('href'),
131444                 target = record.get('hrefTarget'),
131445                 cls = record.get('cls');
131446
131447             while (record) {
131448                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
131449                     if (record.getDepth() === depth) {
131450                         buf.unshift(format(imgText,
131451                             treePrefix + 'icon ' + 
131452                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
131453                             (record.get('iconCls') || ''),
131454                             record.get('icon') || Ext.BLANK_IMAGE_URL
131455                         ));
131456                         if (record.get('checked') !== null) {
131457                             buf.unshift(format(
131458                                 checkboxText,
131459                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
131460                                 record.get('checked') ? 'aria-checked="true"' : ''
131461                             ));
131462                             if (record.get('checked')) {
131463                                 metaData.tdCls += (' ' + treePrefix + 'checked');
131464                             }
131465                         }
131466                         if (record.isLast()) {
131467                             if (record.isExpandable()) {
131468                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131469                             } else {
131470                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
131471                             }
131472                             
131473                         } else {
131474                             if (record.isExpandable()) {
131475                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131476                             } else {
131477                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
131478                             }
131479                         }
131480                     } else {
131481                         if (record.isLast() || record.getDepth() === 0) {
131482                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
131483                         } else if (record.getDepth() !== 0) {
131484                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
131485                         }                      
131486                     }
131487                 }
131488                 record = record.parentNode;
131489             }
131490             if (href) {
131491                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
131492             } else {
131493                 buf.push(formattedValue);
131494             }
131495             if (cls) {
131496                 metaData.tdCls += ' ' + cls;
131497             }
131498             return buf.join('');
131499         };
131500         this.callParent(arguments);
131501     },
131502
131503     defaultRenderer: function(value) {
131504         return value;
131505     }
131506 });
131507 /**
131508  * Used as a view by {@link Ext.tree.Panel TreePanel}.
131509  */
131510 Ext.define('Ext.tree.View', {
131511     extend: 'Ext.view.Table',
131512     alias: 'widget.treeview',
131513
131514     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
131515     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
131516
131517     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
131518     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
131519     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
131520
131521     // Class to add to the node wrap element used to hold nodes when a parent is being
131522     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
131523     // for an ancestor node with this class.
131524     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
131525
131526     blockRefresh: true,
131527
131528     /** 
131529      * @cfg {Boolean} rootVisible
131530      * False to hide the root node.
131531      */
131532     rootVisible: true,
131533
131534     /** 
131535      * @cfg {Boolean} animate
131536      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
131537      */
131538
131539     expandDuration: 250,
131540     collapseDuration: 250,
131541     
131542     toggleOnDblClick: true,
131543
131544     initComponent: function() {
131545         var me = this;
131546         
131547         if (me.initialConfig.animate === undefined) {
131548             me.animate = Ext.enableFx;
131549         }
131550         
131551         me.store = Ext.create('Ext.data.NodeStore', {
131552             recursive: true,
131553             rootVisible: me.rootVisible,
131554             listeners: {
131555                 beforeexpand: me.onBeforeExpand,
131556                 expand: me.onExpand,
131557                 beforecollapse: me.onBeforeCollapse,
131558                 collapse: me.onCollapse,
131559                 scope: me
131560             }
131561         });
131562         
131563         if (me.node) {
131564             me.setRootNode(me.node);
131565         }
131566         me.animQueue = {};
131567         me.callParent(arguments);
131568     },
131569
131570     processUIEvent: function(e) {
131571         // If the clicked node is part of an animation, ignore the click.
131572         // This is because during a collapse animation, the associated Records
131573         // will already have been removed from the Store, and the event is not processable.
131574         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
131575             return false;
131576         }
131577         return this.callParent(arguments);
131578     },
131579
131580     onClear: function(){
131581         this.store.removeAll();    
131582     },
131583
131584     setRootNode: function(node) {
131585         var me = this;        
131586         me.store.setNode(node);
131587         me.node = node;
131588         if (!me.rootVisible) {
131589             node.expand();
131590         }
131591     },
131592     
131593     onRender: function() {
131594         var me = this,
131595             el;
131596
131597         me.callParent(arguments);
131598
131599         el = me.el;
131600         el.on({
131601             scope: me,
131602             delegate: me.expanderSelector,
131603             mouseover: me.onExpanderMouseOver,
131604             mouseout: me.onExpanderMouseOut
131605         });
131606         el.on({
131607             scope: me,
131608             delegate: me.checkboxSelector,
131609             click: me.onCheckboxChange
131610         });
131611     },
131612
131613     onCheckboxChange: function(e, t) {
131614         var me = this,
131615             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
131616             
131617         if (item) {
131618             me.onCheckChange(me.getRecord(item));
131619         }
131620     },
131621     
131622     onCheckChange: function(record){
131623         var checked = record.get('checked');
131624         if (Ext.isBoolean(checked)) {
131625             checked = !checked;
131626             record.set('checked', checked);
131627             this.fireEvent('checkchange', record, checked);
131628         }
131629     },
131630
131631     getChecked: function() {
131632         var checked = [];
131633         this.node.cascadeBy(function(rec){
131634             if (rec.get('checked')) {
131635                 checked.push(rec);
131636             }
131637         });
131638         return checked;
131639     },
131640     
131641     isItemChecked: function(rec){
131642         return rec.get('checked');
131643     },
131644
131645     createAnimWrap: function(record, index) {
131646         var thHtml = '',
131647             headerCt = this.panel.headerCt,
131648             headers = headerCt.getGridColumns(),
131649             i = 0, len = headers.length, item,
131650             node = this.getNode(record),
131651             tmpEl, nodeEl;
131652
131653         for (; i < len; i++) {
131654             item = headers[i];
131655             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
131656         }
131657
131658         nodeEl = Ext.get(node);        
131659         tmpEl = nodeEl.insertSibling({
131660             tag: 'tr',
131661             html: [
131662                 '<td colspan="' + headerCt.getColumnCount() + '">',
131663                     '<div class="' + this.nodeAnimWrapCls + '">',
131664                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
131665                             thHtml,
131666                         '</tbody></table>',
131667                     '</div>',
131668                 '</td>'
131669             ].join('')
131670         }, 'after');
131671
131672         return {
131673             record: record,
131674             node: node,
131675             el: tmpEl,
131676             expanding: false,
131677             collapsing: false,
131678             animating: false,
131679             animateEl: tmpEl.down('div'),
131680             targetEl: tmpEl.down('tbody')
131681         };
131682     },
131683
131684     getAnimWrap: function(parent) {
131685         if (!this.animate) {
131686             return null;
131687         }
131688
131689         // We are checking to see which parent is having the animation wrap
131690         while (parent) {
131691             if (parent.animWrap) {
131692                 return parent.animWrap;
131693             }
131694             parent = parent.parentNode;
131695         }
131696         return null;
131697     },
131698
131699     doAdd: function(nodes, records, index) {
131700         // If we are adding records which have a parent that is currently expanding
131701         // lets add them to the animation wrap
131702         var me = this,
131703             record = records[0],
131704             parent = record.parentNode,
131705             a = me.all.elements,
131706             relativeIndex = 0,
131707             animWrap = me.getAnimWrap(parent),
131708             targetEl, children, len;
131709
131710         if (!animWrap || !animWrap.expanding) {
131711             me.resetScrollers();
131712             return me.callParent(arguments);
131713         }
131714
131715         // We need the parent that has the animWrap, not the nodes parent
131716         parent = animWrap.record;
131717         
131718         // If there is an anim wrap we do our special magic logic
131719         targetEl = animWrap.targetEl;
131720         children = targetEl.dom.childNodes;
131721         
131722         // We subtract 1 from the childrens length because we have a tr in there with the th'es
131723         len = children.length - 1;
131724         
131725         // The relative index is the index in the full flat collection minus the index of the wraps parent
131726         relativeIndex = index - me.indexOf(parent) - 1;
131727         
131728         // If we are adding records to the wrap that have a higher relative index then there are currently children
131729         // it means we have to append the nodes to the wrap
131730         if (!len || relativeIndex >= len) {
131731             targetEl.appendChild(nodes);
131732         }
131733         // If there are already more children then the relative index it means we are adding child nodes of
131734         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
131735         else {
131736             // +1 because of the tr with th'es that is already there
131737             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
131738         }
131739
131740         // We also have to update the CompositeElementLite collection of the DataView
131741         Ext.Array.insert(a, index, nodes);
131742         
131743         // If we were in an animation we need to now change the animation
131744         // because the targetEl just got higher.
131745         if (animWrap.isAnimating) {
131746             me.onExpand(parent);
131747         }
131748     },
131749     
131750     beginBulkUpdate: function(){
131751         this.bulkUpdate = true;
131752         this.ownerCt.changingScrollbars = true;  
131753     },
131754     
131755     endBulkUpdate: function(){
131756         var me = this,
131757             ownerCt = me.ownerCt;
131758         
131759         me.bulkUpdate = false;
131760         me.ownerCt.changingScrollbars = true;  
131761         me.resetScrollers();  
131762     },
131763     
131764     onRemove : function(ds, record, index) {
131765         var me = this,
131766             bulk = me.bulkUpdate;
131767
131768         me.doRemove(record, index);
131769         if (!bulk) {
131770             me.updateIndexes(index);
131771         }
131772         if (me.store.getCount() === 0){
131773             me.refresh();
131774         }
131775         if (!bulk) {
131776             me.fireEvent('itemremove', record, index);
131777         }
131778     },
131779     
131780     doRemove: function(record, index) {
131781         // If we are adding records which have a parent that is currently expanding
131782         // lets add them to the animation wrap
131783         var me = this,
131784             parent = record.parentNode,
131785             all = me.all,
131786             animWrap = me.getAnimWrap(record),
131787             node = all.item(index).dom;
131788
131789         if (!animWrap || !animWrap.collapsing) {
131790             me.resetScrollers();
131791             return me.callParent(arguments);
131792         }
131793
131794         animWrap.targetEl.appendChild(node);
131795         all.removeElement(index);
131796     },
131797
131798     onBeforeExpand: function(parent, records, index) {
131799         var me = this,
131800             animWrap;
131801             
131802         if (!me.rendered || !me.animate) {
131803             return;
131804         }
131805
131806         if (me.getNode(parent)) {
131807             animWrap = me.getAnimWrap(parent);
131808             if (!animWrap) {
131809                 animWrap = parent.animWrap = me.createAnimWrap(parent);
131810                 animWrap.animateEl.setHeight(0);
131811             }
131812             else if (animWrap.collapsing) {
131813                 // If we expand this node while it is still expanding then we
131814                 // have to remove the nodes from the animWrap.
131815                 animWrap.targetEl.select(me.itemSelector).remove();
131816             } 
131817             animWrap.expanding = true;
131818             animWrap.collapsing = false;
131819         }
131820     },
131821
131822     onExpand: function(parent) {
131823         var me = this,
131824             queue = me.animQueue,
131825             id = parent.getId(),
131826             animWrap,
131827             animateEl, 
131828             targetEl,
131829             queueItem;        
131830         
131831         if (me.singleExpand) {
131832             me.ensureSingleExpand(parent);
131833         }
131834         
131835         animWrap = me.getAnimWrap(parent);
131836
131837         if (!animWrap) {
131838             me.resetScrollers();
131839             return;
131840         }
131841         
131842         animateEl = animWrap.animateEl;
131843         targetEl = animWrap.targetEl;
131844
131845         animateEl.stopAnimation();
131846         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131847         queue[id] = true;
131848         animateEl.slideIn('t', {
131849             duration: me.expandDuration,
131850             listeners: {
131851                 scope: me,
131852                 lastframe: function() {
131853                     // Move all the nodes out of the anim wrap to their proper location
131854                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
131855                     animWrap.el.remove();
131856                     me.resetScrollers();
131857                     delete animWrap.record.animWrap;
131858                     delete queue[id];
131859                 }
131860             }
131861         });
131862         
131863         animWrap.isAnimating = true;
131864     },
131865     
131866     resetScrollers: function(){
131867         if (!this.bulkUpdate) {
131868             var panel = this.panel;
131869             
131870             panel.determineScrollbars();
131871             panel.invalidateScroller();
131872         }
131873     },
131874
131875     onBeforeCollapse: function(parent, records, index) {
131876         var me = this,
131877             animWrap;
131878             
131879         if (!me.rendered || !me.animate) {
131880             return;
131881         }
131882
131883         if (me.getNode(parent)) {
131884             animWrap = me.getAnimWrap(parent);
131885             if (!animWrap) {
131886                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
131887             }
131888             else if (animWrap.expanding) {
131889                 // If we collapse this node while it is still expanding then we
131890                 // have to remove the nodes from the animWrap.
131891                 animWrap.targetEl.select(this.itemSelector).remove();
131892             }
131893             animWrap.expanding = false;
131894             animWrap.collapsing = true;
131895         }
131896     },
131897     
131898     onCollapse: function(parent) {
131899         var me = this,
131900             queue = me.animQueue,
131901             id = parent.getId(),
131902             animWrap = me.getAnimWrap(parent),
131903             animateEl, targetEl;
131904
131905         if (!animWrap) {
131906             me.resetScrollers();
131907             return;
131908         }
131909         
131910         animateEl = animWrap.animateEl;
131911         targetEl = animWrap.targetEl;
131912
131913         queue[id] = true;
131914         
131915         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131916         animateEl.stopAnimation();
131917         animateEl.slideOut('t', {
131918             duration: me.collapseDuration,
131919             listeners: {
131920                 scope: me,
131921                 lastframe: function() {
131922                     animWrap.el.remove();
131923                     delete animWrap.record.animWrap;
131924                     me.resetScrollers();
131925                     delete queue[id];
131926                 }             
131927             }
131928         });
131929         animWrap.isAnimating = true;
131930     },
131931     
131932     /**
131933      * Checks if a node is currently undergoing animation
131934      * @private
131935      * @param {Ext.data.Model} node The node
131936      * @return {Boolean} True if the node is animating
131937      */
131938     isAnimating: function(node) {
131939         return !!this.animQueue[node.getId()];    
131940     },
131941     
131942     collectData: function(records) {
131943         var data = this.callParent(arguments),
131944             rows = data.rows,
131945             len = rows.length,
131946             i = 0,
131947             row, record;
131948             
131949         for (; i < len; i++) {
131950             row = rows[i];
131951             record = records[i];
131952             if (record.get('qtip')) {
131953                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
131954                 if (record.get('qtitle')) {
131955                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
131956                 }
131957             }
131958             if (record.isExpanded()) {
131959                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
131960             }
131961             if (record.isLoading()) {
131962                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
131963             }
131964         }
131965         
131966         return data;
131967     },
131968     
131969     /**
131970      * Expands a record that is loaded in the view.
131971      * @param {Ext.data.Model} record The record to expand
131972      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
131973      * @param {Function} callback (optional) The function to run after the expand is completed
131974      * @param {Object} scope (optional) The scope of the callback function.
131975      */
131976     expand: function(record, deep, callback, scope) {
131977         return record.expand(deep, callback, scope);
131978     },
131979     
131980     /**
131981      * Collapses a record that is loaded in the view.
131982      * @param {Ext.data.Model} record The record to collapse
131983      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
131984      * @param {Function} callback (optional) The function to run after the collapse is completed
131985      * @param {Object} scope (optional) The scope of the callback function.
131986      */
131987     collapse: function(record, deep, callback, scope) {
131988         return record.collapse(deep, callback, scope);
131989     },
131990     
131991     /**
131992      * Toggles a record between expanded and collapsed.
131993      * @param {Ext.data.Model} recordInstance
131994      */
131995     toggle: function(record) {
131996         this[record.isExpanded() ? 'collapse' : 'expand'](record);
131997     },
131998     
131999     onItemDblClick: function(record, item, index) {
132000         this.callParent(arguments);
132001         if (this.toggleOnDblClick) {
132002             this.toggle(record);
132003         }
132004     },
132005     
132006     onBeforeItemMouseDown: function(record, item, index, e) {
132007         if (e.getTarget(this.expanderSelector, item)) {
132008             return false;
132009         }
132010         return this.callParent(arguments);
132011     },
132012     
132013     onItemClick: function(record, item, index, e) {
132014         if (e.getTarget(this.expanderSelector, item)) {
132015             this.toggle(record);
132016             return false;
132017         }
132018         return this.callParent(arguments);
132019     },
132020     
132021     onExpanderMouseOver: function(e, t) {
132022         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
132023     },
132024     
132025     onExpanderMouseOut: function(e, t) {
132026         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
132027     },
132028     
132029     /**
132030      * Gets the base TreeStore from the bound TreePanel.
132031      */
132032     getTreeStore: function() {
132033         return this.panel.store;
132034     },    
132035     
132036     ensureSingleExpand: function(node) {
132037         var parent = node.parentNode;
132038         if (parent) {
132039             parent.eachChild(function(child) {
132040                 if (child !== node && child.isExpanded()) {
132041                     child.collapse();
132042                 }
132043             });
132044         }
132045     }
132046 });
132047 /**
132048  * The TreePanel provides tree-structured UI representation of tree-structured data.
132049  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
132050  * multiple columns through the {@link #columns} configuration.
132051  *
132052  * Simple TreePanel using inline data:
132053  *
132054  *     @example
132055  *     var store = Ext.create('Ext.data.TreeStore', {
132056  *         root: {
132057  *             expanded: true,
132058  *             children: [
132059  *                 { text: "detention", leaf: true },
132060  *                 { text: "homework", expanded: true, children: [
132061  *                     { text: "book report", leaf: true },
132062  *                     { text: "alegrbra", leaf: true}
132063  *                 ] },
132064  *                 { text: "buy lottery tickets", leaf: true }
132065  *             ]
132066  *         }
132067  *     });
132068  *
132069  *     Ext.create('Ext.tree.Panel', {
132070  *         title: 'Simple Tree',
132071  *         width: 200,
132072  *         height: 150,
132073  *         store: store,
132074  *         rootVisible: false,
132075  *         renderTo: Ext.getBody()
132076  *     });
132077  *
132078  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
132079  * {@link Ext.data.NodeInterface NodeInterface} config options.
132080  */
132081 Ext.define('Ext.tree.Panel', {
132082     extend: 'Ext.panel.Table',
132083     alias: 'widget.treepanel',
132084     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
132085     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
132086     viewType: 'treeview',
132087     selType: 'treemodel',
132088
132089     treeCls: Ext.baseCSSPrefix + 'tree-panel',
132090
132091     deferRowRender: false,
132092
132093     /**
132094      * @cfg {Boolean} lines False to disable tree lines.
132095      */
132096     lines: true,
132097
132098     /**
132099      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
132100      */
132101     useArrows: false,
132102
132103     /**
132104      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
132105      */
132106     singleExpand: false,
132107
132108     ddConfig: {
132109         enableDrag: true,
132110         enableDrop: true
132111     },
132112
132113     /**
132114      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
132115      */
132116
132117     /**
132118      * @cfg {Boolean} rootVisible False to hide the root node.
132119      */
132120     rootVisible: true,
132121
132122     /**
132123      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
132124      */
132125     displayField: 'text',
132126
132127     /**
132128      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
132129      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
132130      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
132131      * to that store. For example:
132132      *
132133      *     Ext.create('Ext.tree.Panel', {
132134      *         title: 'Simple Tree',
132135      *         root: {
132136      *             text: "Root node",
132137      *             expanded: true,
132138      *             children: [
132139      *                 { text: "Child 1", leaf: true },
132140      *                 { text: "Child 2", leaf: true }
132141      *             ]
132142      *         },
132143      *         renderTo: Ext.getBody()
132144      *     });
132145      */
132146     root: null,
132147
132148     // Required for the Lockable Mixin. These are the configurations which will be copied to the
132149     // normal and locked sub tablepanels
132150     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
132151     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
132152
132153     /**
132154      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
132155      */
132156
132157     /**
132158      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
132159      */
132160
132161     constructor: function(config) {
132162         config = config || {};
132163         if (config.animate === undefined) {
132164             config.animate = Ext.enableFx;
132165         }
132166         this.enableAnimations = config.animate;
132167         delete config.animate;
132168
132169         this.callParent([config]);
132170     },
132171
132172     initComponent: function() {
132173         var me = this,
132174             cls = [me.treeCls];
132175
132176         if (me.useArrows) {
132177             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
132178             me.lines = false;
132179         }
132180
132181         if (me.lines) {
132182             cls.push(Ext.baseCSSPrefix + 'tree-lines');
132183         } else if (!me.useArrows) {
132184             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
132185         }
132186
132187         if (Ext.isString(me.store)) {
132188             me.store = Ext.StoreMgr.lookup(me.store);
132189         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
132190             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
132191                 root: me.root,
132192                 fields: me.fields,
132193                 model: me.model,
132194                 folderSort: me.folderSort
132195             }));
132196         } else if (me.root) {
132197             me.store = Ext.data.StoreManager.lookup(me.store);
132198             me.store.setRootNode(me.root);
132199             if (me.folderSort !== undefined) {
132200                 me.store.folderSort = me.folderSort;
132201                 me.store.sort();
132202             }
132203         }
132204
132205         // I'm not sure if we want to this. It might be confusing
132206         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
132207         //     me.rootVisible = false;
132208         // }
132209
132210         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
132211             rootVisible: me.rootVisible,
132212             animate: me.enableAnimations,
132213             singleExpand: me.singleExpand,
132214             node: me.store.getRootNode(),
132215             hideHeaders: me.hideHeaders
132216         });
132217
132218         me.mon(me.store, {
132219             scope: me,
132220             rootchange: me.onRootChange,
132221             clear: me.onClear
132222         });
132223
132224         me.relayEvents(me.store, [
132225             /**
132226              * @event beforeload
132227              * @alias Ext.data.Store#beforeload
132228              */
132229             'beforeload',
132230
132231             /**
132232              * @event load
132233              * @alias Ext.data.Store#load
132234              */
132235             'load'
132236         ]);
132237
132238         me.store.on({
132239             /**
132240              * @event itemappend
132241              * @alias Ext.data.TreeStore#append
132242              */
132243             append: me.createRelayer('itemappend'),
132244
132245             /**
132246              * @event itemremove
132247              * @alias Ext.data.TreeStore#remove
132248              */
132249             remove: me.createRelayer('itemremove'),
132250
132251             /**
132252              * @event itemmove
132253              * @alias Ext.data.TreeStore#move
132254              */
132255             move: me.createRelayer('itemmove'),
132256
132257             /**
132258              * @event iteminsert
132259              * @alias Ext.data.TreeStore#insert
132260              */
132261             insert: me.createRelayer('iteminsert'),
132262
132263             /**
132264              * @event beforeitemappend
132265              * @alias Ext.data.TreeStore#beforeappend
132266              */
132267             beforeappend: me.createRelayer('beforeitemappend'),
132268
132269             /**
132270              * @event beforeitemremove
132271              * @alias Ext.data.TreeStore#beforeremove
132272              */
132273             beforeremove: me.createRelayer('beforeitemremove'),
132274
132275             /**
132276              * @event beforeitemmove
132277              * @alias Ext.data.TreeStore#beforemove
132278              */
132279             beforemove: me.createRelayer('beforeitemmove'),
132280
132281             /**
132282              * @event beforeiteminsert
132283              * @alias Ext.data.TreeStore#beforeinsert
132284              */
132285             beforeinsert: me.createRelayer('beforeiteminsert'),
132286
132287             /**
132288              * @event itemexpand
132289              * @alias Ext.data.TreeStore#expand
132290              */
132291             expand: me.createRelayer('itemexpand'),
132292
132293             /**
132294              * @event itemcollapse
132295              * @alias Ext.data.TreeStore#collapse
132296              */
132297             collapse: me.createRelayer('itemcollapse'),
132298
132299             /**
132300              * @event beforeitemexpand
132301              * @alias Ext.data.TreeStore#beforeexpand
132302              */
132303             beforeexpand: me.createRelayer('beforeitemexpand'),
132304
132305             /**
132306              * @event beforeitemcollapse
132307              * @alias Ext.data.TreeStore#beforecollapse
132308              */
132309             beforecollapse: me.createRelayer('beforeitemcollapse')
132310         });
132311
132312         // If the user specifies the headers collection manually then dont inject our own
132313         if (!me.columns) {
132314             if (me.initialConfig.hideHeaders === undefined) {
132315                 me.hideHeaders = true;
132316             }
132317             me.columns = [{
132318                 xtype    : 'treecolumn',
132319                 text     : 'Name',
132320                 flex     : 1,
132321                 dataIndex: me.displayField
132322             }];
132323         }
132324
132325         if (me.cls) {
132326             cls.push(me.cls);
132327         }
132328         me.cls = cls.join(' ');
132329         me.callParent();
132330
132331         me.relayEvents(me.getView(), [
132332             /**
132333              * @event checkchange
132334              * Fires when a node with a checkbox's checked property changes
132335              * @param {Ext.data.Model} node The node who's checked property was changed
132336              * @param {Boolean} checked The node's new checked state
132337              */
132338             'checkchange'
132339         ]);
132340
132341         // If the root is not visible and there is no rootnode defined, then just lets load the store
132342         if (!me.getView().rootVisible && !me.getRootNode()) {
132343             me.setRootNode({
132344                 expanded: true
132345             });
132346         }
132347     },
132348
132349     onClear: function(){
132350         this.view.onClear();
132351     },
132352
132353     /**
132354      * Sets root node of this tree.
132355      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
132356      * @return {Ext.data.NodeInterface} The new root
132357      */
132358     setRootNode: function() {
132359         return this.store.setRootNode.apply(this.store, arguments);
132360     },
132361
132362     /**
132363      * Returns the root node for this tree.
132364      * @return {Ext.data.NodeInterface}
132365      */
132366     getRootNode: function() {
132367         return this.store.getRootNode();
132368     },
132369
132370     onRootChange: function(root) {
132371         this.view.setRootNode(root);
132372     },
132373
132374     /**
132375      * Retrieve an array of checked records.
132376      * @return {Ext.data.Model[]} An array containing the checked records
132377      */
132378     getChecked: function() {
132379         return this.getView().getChecked();
132380     },
132381
132382     isItemChecked: function(rec) {
132383         return rec.get('checked');
132384     },
132385
132386     /**
132387      * Expand all nodes
132388      * @param {Function} callback (optional) A function to execute when the expand finishes.
132389      * @param {Object} scope (optional) The scope of the callback function
132390      */
132391     expandAll : function(callback, scope) {
132392         var root = this.getRootNode(),
132393             animate = this.enableAnimations,
132394             view = this.getView();
132395         if (root) {
132396             if (!animate) {
132397                 view.beginBulkUpdate();
132398             }
132399             root.expand(true, callback, scope);
132400             if (!animate) {
132401                 view.endBulkUpdate();
132402             }
132403         }
132404     },
132405
132406     /**
132407      * Collapse all nodes
132408      * @param {Function} callback (optional) A function to execute when the collapse finishes.
132409      * @param {Object} scope (optional) The scope of the callback function
132410      */
132411     collapseAll : function(callback, scope) {
132412         var root = this.getRootNode(),
132413             animate = this.enableAnimations,
132414             view = this.getView();
132415
132416         if (root) {
132417             if (!animate) {
132418                 view.beginBulkUpdate();
132419             }
132420             if (view.rootVisible) {
132421                 root.collapse(true, callback, scope);
132422             } else {
132423                 root.collapseChildren(true, callback, scope);
132424             }
132425             if (!animate) {
132426                 view.endBulkUpdate();
132427             }
132428         }
132429     },
132430
132431     /**
132432      * Expand the tree to the path of a particular node.
132433      * @param {String} path The path to expand. The path should include a leading separator.
132434      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132435      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132436      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
132437      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
132438      * @param {Object} scope (optional) The scope of the callback function
132439      */
132440     expandPath: function(path, field, separator, callback, scope) {
132441         var me = this,
132442             current = me.getRootNode(),
132443             index = 1,
132444             view = me.getView(),
132445             keys,
132446             expander;
132447
132448         field = field || me.getRootNode().idProperty;
132449         separator = separator || '/';
132450
132451         if (Ext.isEmpty(path)) {
132452             Ext.callback(callback, scope || me, [false, null]);
132453             return;
132454         }
132455
132456         keys = path.split(separator);
132457         if (current.get(field) != keys[1]) {
132458             // invalid root
132459             Ext.callback(callback, scope || me, [false, current]);
132460             return;
132461         }
132462
132463         expander = function(){
132464             if (++index === keys.length) {
132465                 Ext.callback(callback, scope || me, [true, current]);
132466                 return;
132467             }
132468             var node = current.findChild(field, keys[index]);
132469             if (!node) {
132470                 Ext.callback(callback, scope || me, [false, current]);
132471                 return;
132472             }
132473             current = node;
132474             current.expand(false, expander);
132475         };
132476         current.expand(false, expander);
132477     },
132478
132479     /**
132480      * Expand the tree to the path of a particular node, then select it.
132481      * @param {String} path The path to select. The path should include a leading separator.
132482      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132483      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132484      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
132485      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
132486      * @param {Object} scope (optional) The scope of the callback function
132487      */
132488     selectPath: function(path, field, separator, callback, scope) {
132489         var me = this,
132490             keys,
132491             last;
132492
132493         field = field || me.getRootNode().idProperty;
132494         separator = separator || '/';
132495
132496         keys = path.split(separator);
132497         last = keys.pop();
132498
132499         me.expandPath(keys.join(separator), field, separator, function(success, node){
132500             var doSuccess = false;
132501             if (success && node) {
132502                 node = node.findChild(field, last);
132503                 if (node) {
132504                     me.getSelectionModel().select(node);
132505                     Ext.callback(callback, scope || me, [true, node]);
132506                     doSuccess = true;
132507                 }
132508             } else if (node === me.getRootNode()) {
132509                 doSuccess = true;
132510             }
132511             Ext.callback(callback, scope || me, [doSuccess, node]);
132512         }, me);
132513     }
132514 });
132515
132516 /**
132517  * @class Ext.view.DragZone
132518  * @extends Ext.dd.DragZone
132519  * @private
132520  */
132521 Ext.define('Ext.view.DragZone', {
132522     extend: 'Ext.dd.DragZone',
132523     containerScroll: false,
132524
132525     constructor: function(config) {
132526         var me = this;
132527
132528         Ext.apply(me, config);
132529
132530         // Create a ddGroup unless one has been configured.
132531         // User configuration of ddGroups allows users to specify which
132532         // DD instances can interact with each other. Using one
132533         // based on the id of the View would isolate it and mean it can only
132534         // interact with a DropZone on the same View also using a generated ID.
132535         if (!me.ddGroup) {
132536             me.ddGroup = 'view-dd-zone-' + me.view.id;
132537         }
132538
132539         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
132540         // So a View's DragZone cannot use the View's main element because the DropZone must use that
132541         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
132542         // main element which handles scrolling.
132543         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
132544         // is transient; DataView's recreate the internal structure dynamically as data changes.
132545         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
132546         me.callParent([me.view.el.dom.parentNode]);
132547
132548         me.ddel = Ext.get(document.createElement('div'));
132549         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
132550     },
132551
132552     init: function(id, sGroup, config) {
132553         this.initTarget(id, sGroup, config);
132554         this.view.mon(this.view, {
132555             itemmousedown: this.onItemMouseDown,
132556             scope: this
132557         });
132558     },
132559
132560     onItemMouseDown: function(view, record, item, index, e) {
132561         if (!this.isPreventDrag(e, record, item, index)) {
132562             this.handleMouseDown(e);
132563
132564             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
132565             // if the mousedowned record is selected
132566             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
132567                 return false;
132568             }
132569         }
132570     },
132571
132572     // private template method
132573     isPreventDrag: function(e) {
132574         return false;
132575     },
132576
132577     getDragData: function(e) {
132578         var view = this.view,
132579             item = e.getTarget(view.getItemSelector()),
132580             record, selectionModel, records;
132581
132582         if (item) {
132583             record = view.getRecord(item);
132584             selectionModel = view.getSelectionModel();
132585             records = selectionModel.getSelection();
132586             return {
132587                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
132588                 event: new Ext.EventObjectImpl(e),
132589                 view: view,
132590                 ddel: this.ddel,
132591                 item: item,
132592                 records: records,
132593                 fromPosition: Ext.fly(item).getXY()
132594             };
132595         }
132596     },
132597
132598     onInitDrag: function(x, y) {
132599         var me = this,
132600             data = me.dragData,
132601             view = data.view,
132602             selectionModel = view.getSelectionModel(),
132603             record = view.getRecord(data.item),
132604             e = data.event;
132605
132606         // Update the selection to match what would have been selected if the user had
132607         // done a full click on the target node rather than starting a drag from it
132608         if (!selectionModel.isSelected(record) || e.hasModifier()) {
132609             selectionModel.selectWithEvent(record, e, true);
132610         }
132611         data.records = selectionModel.getSelection();
132612
132613         me.ddel.update(me.getDragText());
132614         me.proxy.update(me.ddel.dom);
132615         me.onStartDrag(x, y);
132616         return true;
132617     },
132618
132619     getDragText: function() {
132620         var count = this.dragData.records.length;
132621         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
132622     },
132623
132624     getRepairXY : function(e, data){
132625         return data ? data.fromPosition : false;
132626     }
132627 });
132628 Ext.define('Ext.tree.ViewDragZone', {
132629     extend: 'Ext.view.DragZone',
132630
132631     isPreventDrag: function(e, record) {
132632         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
132633     },
132634     
132635     afterRepair: function() {
132636         var me = this,
132637             view = me.view,
132638             selectedRowCls = view.selectedItemCls,
132639             records = me.dragData.records,
132640             fly = Ext.fly;
132641         
132642         if (Ext.enableFx && me.repairHighlight) {
132643             // Roll through all records and highlight all the ones we attempted to drag.
132644             Ext.Array.forEach(records, function(record) {
132645                 // anonymous fns below, don't hoist up unless below is wrapped in
132646                 // a self-executing function passing in item.
132647                 var item = view.getNode(record);
132648                 
132649                 // We must remove the selected row class before animating, because
132650                 // the selected row class declares !important on its background-color.
132651                 fly(item.firstChild).highlight(me.repairHighlightColor, {
132652                     listeners: {
132653                         beforeanimate: function() {
132654                             if (view.isSelected(item)) {
132655                                 fly(item).removeCls(selectedRowCls);
132656                             }
132657                         },
132658                         afteranimate: function() {
132659                             if (view.isSelected(item)) {
132660                                 fly(item).addCls(selectedRowCls);
132661                             }
132662                         }
132663                     }
132664                 });
132665             });
132666         }
132667         me.dragging = false;
132668     }
132669 });
132670 /**
132671  * @class Ext.tree.ViewDropZone
132672  * @extends Ext.view.DropZone
132673  * @private
132674  */
132675 Ext.define('Ext.tree.ViewDropZone', {
132676     extend: 'Ext.view.DropZone',
132677
132678     /**
132679      * @cfg {Boolean} allowParentInsert
132680      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
132681      * sibling of the parent when dropped.
132682      */
132683     allowParentInserts: false,
132684  
132685     /**
132686      * @cfg {String} allowContainerDrop
132687      * True if drops on the tree container (outside of a specific tree node) are allowed.
132688      */
132689     allowContainerDrops: false,
132690
132691     /**
132692      * @cfg {String} appendOnly
132693      * True if the tree should only allow append drops (use for trees which are sorted).
132694      */
132695     appendOnly: false,
132696
132697     /**
132698      * @cfg {String} expandDelay
132699      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
132700      * over the target.
132701      */
132702     expandDelay : 500,
132703
132704     indicatorCls: 'x-tree-ddindicator',
132705
132706     // private
132707     expandNode : function(node) {
132708         var view = this.view;
132709         if (!node.isLeaf() && !node.isExpanded()) {
132710             view.expand(node);
132711             this.expandProcId = false;
132712         }
132713     },
132714
132715     // private
132716     queueExpand : function(node) {
132717         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
132718     },
132719
132720     // private
132721     cancelExpand : function() {
132722         if (this.expandProcId) {
132723             clearTimeout(this.expandProcId);
132724             this.expandProcId = false;
132725         }
132726     },
132727
132728     getPosition: function(e, node) {
132729         var view = this.view,
132730             record = view.getRecord(node),
132731             y = e.getPageY(),
132732             noAppend = record.isLeaf(),
132733             noBelow = false,
132734             region = Ext.fly(node).getRegion(),
132735             fragment;
132736
132737         // If we are dragging on top of the root node of the tree, we always want to append.
132738         if (record.isRoot()) {
132739             return 'append';
132740         }
132741
132742         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
132743         if (this.appendOnly) {
132744             return noAppend ? false : 'append';
132745         }
132746
132747         if (!this.allowParentInsert) {
132748             noBelow = record.hasChildNodes() && record.isExpanded();
132749         }
132750
132751         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
132752         if (y >= region.top && y < (region.top + fragment)) {
132753             return 'before';
132754         }
132755         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
132756             return 'after';
132757         }
132758         else {
132759             return 'append';
132760         }
132761     },
132762
132763     isValidDropPoint : function(node, position, dragZone, e, data) {
132764         if (!node || !data.item) {
132765             return false;
132766         }
132767
132768         var view = this.view,
132769             targetNode = view.getRecord(node),
132770             draggedRecords = data.records,
132771             dataLength = draggedRecords.length,
132772             ln = draggedRecords.length,
132773             i, record;
132774
132775         // No drop position, or dragged records: invalid drop point
132776         if (!(targetNode && position && dataLength)) {
132777             return false;
132778         }
132779
132780         // If the targetNode is within the folder we are dragging
132781         for (i = 0; i < ln; i++) {
132782             record = draggedRecords[i];
132783             if (record.isNode && record.contains(targetNode)) {
132784                 return false;
132785             }
132786         }
132787         
132788         // Respect the allowDrop field on Tree nodes
132789         if (position === 'append' && targetNode.get('allowDrop') === false) {
132790             return false;
132791         }
132792         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
132793             return false;
132794         }
132795
132796         // If the target record is in the dragged dataset, then invalid drop
132797         if (Ext.Array.contains(draggedRecords, targetNode)) {
132798              return false;
132799         }
132800
132801         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
132802         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
132803         return true;
132804     },
132805
132806     onNodeOver : function(node, dragZone, e, data) {
132807         var position = this.getPosition(e, node),
132808             returnCls = this.dropNotAllowed,
132809             view = this.view,
132810             targetNode = view.getRecord(node),
132811             indicator = this.getIndicator(),
132812             indicatorX = 0,
132813             indicatorY = 0;
132814
132815         // auto node expand check
132816         this.cancelExpand();
132817         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
132818             this.queueExpand(targetNode);
132819         }
132820             
132821             
132822         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
132823             this.valid = true;
132824             this.currentPosition = position;
132825             this.overRecord = targetNode;
132826
132827             indicator.setWidth(Ext.fly(node).getWidth());
132828             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
132829
132830             /*
132831              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
132832              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
132833              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
132834              */
132835             if (position == 'before') {
132836                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132837                 indicator.showAt(0, indicatorY);
132838                 dragZone.proxy.show();
132839             } else if (position == 'after') {
132840                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132841                 indicatorY += Ext.fly(node).getHeight();
132842                 indicator.showAt(0, indicatorY);
132843                 dragZone.proxy.show();
132844             } else {
132845                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
132846                 // @TODO: set a class on the parent folder node to be able to style it
132847                 indicator.hide();
132848             }
132849         } else {
132850             this.valid = false;
132851         }
132852
132853         this.currentCls = returnCls;
132854         return returnCls;
132855     },
132856
132857     onContainerOver : function(dd, e, data) {
132858         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
132859     },
132860     
132861     notifyOut: function() {
132862         this.callParent(arguments);
132863         this.cancelExpand();
132864     },
132865
132866     handleNodeDrop : function(data, targetNode, position) {
132867         var me = this,
132868             view = me.view,
132869             parentNode = targetNode.parentNode,
132870             store = view.getStore(),
132871             recordDomNodes = [],
132872             records, i, len,
132873             insertionMethod, argList,
132874             needTargetExpand,
132875             transferData,
132876             processDrop;
132877
132878         // If the copy flag is set, create a copy of the Models with the same IDs
132879         if (data.copy) {
132880             records = data.records;
132881             data.records = [];
132882             for (i = 0, len = records.length; i < len; i++) {
132883                 data.records.push(Ext.apply({}, records[i].data));
132884             }
132885         }
132886
132887         // Cancel any pending expand operation
132888         me.cancelExpand();
132889
132890         // Grab a reference to the correct node insertion method.
132891         // Create an arg list array intended for the apply method of the
132892         // chosen node insertion method.
132893         // Ensure the target object for the method is referenced by 'targetNode'
132894         if (position == 'before') {
132895             insertionMethod = parentNode.insertBefore;
132896             argList = [null, targetNode];
132897             targetNode = parentNode;
132898         }
132899         else if (position == 'after') {
132900             if (targetNode.nextSibling) {
132901                 insertionMethod = parentNode.insertBefore;
132902                 argList = [null, targetNode.nextSibling];
132903             }
132904             else {
132905                 insertionMethod = parentNode.appendChild;
132906                 argList = [null];
132907             }
132908             targetNode = parentNode;
132909         }
132910         else {
132911             if (!targetNode.isExpanded()) {
132912                 needTargetExpand = true;
132913             }
132914             insertionMethod = targetNode.appendChild;
132915             argList = [null];
132916         }
132917
132918         // A function to transfer the data into the destination tree
132919         transferData = function() {
132920             var node;
132921             for (i = 0, len = data.records.length; i < len; i++) {
132922                 argList[0] = data.records[i];
132923                 node = insertionMethod.apply(targetNode, argList);
132924                 
132925                 if (Ext.enableFx && me.dropHighlight) {
132926                     recordDomNodes.push(view.getNode(node));
132927                 }
132928             }
132929             
132930             // Kick off highlights after everything's been inserted, so they are
132931             // more in sync without insertion/render overhead.
132932             if (Ext.enableFx && me.dropHighlight) {
132933                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
132934                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
132935                 Ext.Array.forEach(recordDomNodes, function(n) {
132936                     if (n) {
132937                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
132938                     }
132939                 });
132940             }
132941         };
132942
132943         // If dropping right on an unexpanded node, transfer the data after it is expanded.
132944         if (needTargetExpand) {
132945             targetNode.expand(false, transferData);
132946         }
132947         // Otherwise, call the data transfer function immediately
132948         else {
132949             transferData();
132950         }
132951     }
132952 });
132953 /**
132954  * This plugin provides drag and/or drop functionality for a TreeView.
132955  *
132956  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
132957  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
132958  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
132959  *
132960  *   - copy : Boolean
132961  *
132962  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
132963  *     the control key was pressed when the drag operation was begun.
132964  *
132965  *   - view : TreeView
132966  *
132967  *     The source TreeView from which the drag originated.
132968  *
132969  *   - ddel : HtmlElement
132970  *
132971  *     The drag proxy element which moves with the mouse
132972  *
132973  *   - item : HtmlElement
132974  *
132975  *     The TreeView node upon which the mousedown event was registered.
132976  *
132977  *   - records : Array
132978  *
132979  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
132980  *
132981  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
132982  * members of the same ddGroup which processes such data objects.
132983  *
132984  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
132985  * {@link #drop}.
132986  *
132987  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
132988  *
132989  *     viewConfig: {
132990  *         plugins: { ptype: 'treeviewdragdrop' }
132991  *     }
132992  */
132993 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
132994     extend: 'Ext.AbstractPlugin',
132995     alias: 'plugin.treeviewdragdrop',
132996
132997     uses: [
132998         'Ext.tree.ViewDragZone',
132999         'Ext.tree.ViewDropZone'
133000     ],
133001
133002     /**
133003      * @event beforedrop
133004      *
133005      * **This event is fired through the TreeView. Add listeners to the TreeView object**
133006      *
133007      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
133008      *
133009      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133010      *
133011      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
133012      * back to the point from which the drag began.
133013      *
133014      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
133015      * was valid, and that the repair operation should not take place.
133016      *
133017      * Any other return value continues with the data transfer operation.
133018      *
133019      * @param {Object} data The data object gathered at mousedown time by the cooperating
133020      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133021      * properties:
133022      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133023      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133024      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133025      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133026      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133027      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133028      * dragged from the source TreeView.
133029      *
133030      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133031      *
133032      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133033      * the midline of the node, or the node is a branch node which accepts new child nodes.
133034      *
133035      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
133036      * Model instances from the source View's Store to the destination View's Store.
133037      *
133038      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
133039      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
133040      *
133041      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
133042      */
133043
133044     /**
133045      * @event drop
133046      *
133047      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
133048      * has been completed and the data has been moved or copied.
133049      *
133050      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133051      *
133052      * @param {Object} data The data object gathered at mousedown time by the cooperating
133053      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133054      * properties:
133055      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133056      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133057      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133058      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133059      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133060      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133061      * dragged from the source TreeView.
133062      *
133063      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133064      *
133065      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133066      * the midline of the node, or the node is a branch node which accepts new child nodes.
133067      */
133068
133069     dragText : '{0} selected node{1}',
133070
133071     /**
133072      * @cfg {Boolean} allowParentInsert
133073      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
133074      * the parent when dropped.
133075      */
133076     allowParentInserts: false,
133077
133078     /**
133079      * @cfg {String} allowContainerDrop
133080      * True if drops on the tree container (outside of a specific tree node) are allowed.
133081      */
133082     allowContainerDrops: false,
133083
133084     /**
133085      * @cfg {String} appendOnly
133086      * True if the tree should only allow append drops (use for trees which are sorted).
133087      */
133088     appendOnly: false,
133089
133090     /**
133091      * @cfg {String} ddGroup
133092      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
133093      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
133094      */
133095     ddGroup : "TreeDD",
133096
133097     /**
133098      * @cfg {String} dragGroup
133099      * The ddGroup to which the DragZone will belong.
133100      *
133101      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
133102      * Drag/DropZones which are members of the same ddGroup.
133103      */
133104
133105     /**
133106      * @cfg {String} dropGroup
133107      * The ddGroup to which the DropZone will belong.
133108      *
133109      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
133110      * Drag/DropZones which are members of the same ddGroup.
133111      */
133112
133113     /**
133114      * @cfg {String} expandDelay
133115      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
133116      * target.
133117      */
133118     expandDelay : 1000,
133119
133120     /**
133121      * @cfg {Boolean} enableDrop
133122      * Set to `false` to disallow the View from accepting drop gestures.
133123      */
133124     enableDrop: true,
133125
133126     /**
133127      * @cfg {Boolean} enableDrag
133128      * Set to `false` to disallow dragging items from the View.
133129      */
133130     enableDrag: true,
133131
133132     /**
133133      * @cfg {String} nodeHighlightColor
133134      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
133135      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
133136      * {@link #nodeHighlightOnRepair}.
133137      */
133138     nodeHighlightColor: 'c3daf9',
133139
133140     /**
133141      * @cfg {Boolean} nodeHighlightOnDrop
133142      * Whether or not to highlight any nodes after they are
133143      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
133144      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
133145      */
133146     nodeHighlightOnDrop: Ext.enableFx,
133147
133148     /**
133149      * @cfg {Boolean} nodeHighlightOnRepair
133150      * Whether or not to highlight any nodes after they are
133151      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
133152      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
133153      */
133154     nodeHighlightOnRepair: Ext.enableFx,
133155
133156     init : function(view) {
133157         view.on('render', this.onViewRender, this, {single: true});
133158     },
133159
133160     /**
133161      * @private
133162      * AbstractComponent calls destroy on all its plugins at destroy time.
133163      */
133164     destroy: function() {
133165         Ext.destroy(this.dragZone, this.dropZone);
133166     },
133167
133168     onViewRender : function(view) {
133169         var me = this;
133170
133171         if (me.enableDrag) {
133172             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
133173                 view: view,
133174                 ddGroup: me.dragGroup || me.ddGroup,
133175                 dragText: me.dragText,
133176                 repairHighlightColor: me.nodeHighlightColor,
133177                 repairHighlight: me.nodeHighlightOnRepair
133178             });
133179         }
133180
133181         if (me.enableDrop) {
133182             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
133183                 view: view,
133184                 ddGroup: me.dropGroup || me.ddGroup,
133185                 allowContainerDrops: me.allowContainerDrops,
133186                 appendOnly: me.appendOnly,
133187                 allowParentInserts: me.allowParentInserts,
133188                 expandDelay: me.expandDelay,
133189                 dropHighlightColor: me.nodeHighlightColor,
133190                 dropHighlight: me.nodeHighlightOnDrop
133191             });
133192         }
133193     }
133194 });
133195 /**
133196  * @class Ext.util.Cookies
133197
133198 Utility class for setting/reading values from browser cookies.
133199 Values can be written using the {@link #set} method.
133200 Values can be read using the {@link #get} method.
133201 A cookie can be invalidated on the client machine using the {@link #clear} method.
133202
133203  * @markdown
133204  * @singleton
133205  */
133206 Ext.define('Ext.util.Cookies', {
133207     singleton: true,
133208     
133209     /**
133210      * Create a cookie with the specified name and value. Additional settings
133211      * for the cookie may be optionally specified (for example: expiration,
133212      * access restriction, SSL).
133213      * @param {String} name The name of the cookie to set. 
133214      * @param {Object} value The value to set for the cookie.
133215      * @param {Object} expires (Optional) Specify an expiration date the
133216      * cookie is to persist until.  Note that the specified Date object will
133217      * be converted to Greenwich Mean Time (GMT). 
133218      * @param {String} path (Optional) Setting a path on the cookie restricts
133219      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
133220      * @param {String} domain (Optional) Setting a domain restricts access to
133221      * pages on a given domain (typically used to allow cookie access across
133222      * subdomains). For example, "sencha.com" will create a cookie that can be
133223      * accessed from any subdomain of sencha.com, including www.sencha.com,
133224      * support.sencha.com, etc.
133225      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
133226      * should only be accessible via SSL on a page using the HTTPS protocol.
133227      * Defaults to <tt>false</tt>. Note that this will only work if the page
133228      * calling this code uses the HTTPS protocol, otherwise the cookie will be
133229      * created with default options.
133230      */
133231     set : function(name, value){
133232         var argv = arguments,
133233             argc = arguments.length,
133234             expires = (argc > 2) ? argv[2] : null,
133235             path = (argc > 3) ? argv[3] : '/',
133236             domain = (argc > 4) ? argv[4] : null,
133237             secure = (argc > 5) ? argv[5] : false;
133238             
133239         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
133240     },
133241
133242     /**
133243      * Retrieves cookies that are accessible by the current page. If a cookie
133244      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
133245      * example retrieves the cookie called "valid" and stores the String value
133246      * in the variable <tt>validStatus</tt>.
133247      * <pre><code>
133248      * var validStatus = Ext.util.Cookies.get("valid");
133249      * </code></pre>
133250      * @param {String} name The name of the cookie to get
133251      * @return {Object} Returns the cookie value for the specified name;
133252      * null if the cookie name does not exist.
133253      */
133254     get : function(name){
133255         var arg = name + "=",
133256             alen = arg.length,
133257             clen = document.cookie.length,
133258             i = 0,
133259             j = 0;
133260             
133261         while(i < clen){
133262             j = i + alen;
133263             if(document.cookie.substring(i, j) == arg){
133264                 return this.getCookieVal(j);
133265             }
133266             i = document.cookie.indexOf(" ", i) + 1;
133267             if(i === 0){
133268                 break;
133269             }
133270         }
133271         return null;
133272     },
133273
133274     /**
133275      * Removes a cookie with the provided name from the browser
133276      * if found by setting its expiration date to sometime in the past. 
133277      * @param {String} name The name of the cookie to remove
133278      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
133279      */
133280     clear : function(name, path){
133281         if(this.get(name)){
133282             path = path || '/';
133283             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
133284         }
133285     },
133286     
133287     /**
133288      * @private
133289      */
133290     getCookieVal : function(offset){
133291         var endstr = document.cookie.indexOf(";", offset);
133292         if(endstr == -1){
133293             endstr = document.cookie.length;
133294         }
133295         return unescape(document.cookie.substring(offset, endstr));
133296     }
133297 });
133298
133299 /**
133300  * @class Ext.util.CSS
133301  * Utility class for manipulating CSS rules
133302  * @singleton
133303  */
133304 Ext.define('Ext.util.CSS', function() {
133305     var rules = null;
133306     var doc = document;
133307
133308     var camelRe = /(-[a-z])/gi;
133309     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
133310
133311     return {
133312
133313         singleton: true,
133314
133315         constructor: function() {
133316             this.rules = {};
133317             this.initialized = false;
133318         },
133319
133320         /**
133321          * Creates a stylesheet from a text blob of rules.
133322          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
133323          * @param {String} cssText The text containing the css rules
133324          * @param {String} id An id to add to the stylesheet for later removal
133325          * @return {CSSStyleSheet}
133326          */
133327         createStyleSheet : function(cssText, id) {
133328             var ss,
133329                 head = doc.getElementsByTagName("head")[0],
133330                 styleEl = doc.createElement("style");
133331
133332             styleEl.setAttribute("type", "text/css");
133333             if (id) {
133334                styleEl.setAttribute("id", id);
133335             }
133336
133337             if (Ext.isIE) {
133338                head.appendChild(styleEl);
133339                ss = styleEl.styleSheet;
133340                ss.cssText = cssText;
133341             } else {
133342                 try{
133343                     styleEl.appendChild(doc.createTextNode(cssText));
133344                 } catch(e) {
133345                    styleEl.cssText = cssText;
133346                 }
133347                 head.appendChild(styleEl);
133348                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
133349             }
133350             this.cacheStyleSheet(ss);
133351             return ss;
133352         },
133353
133354         /**
133355          * Removes a style or link tag by id
133356          * @param {String} id The id of the tag
133357          */
133358         removeStyleSheet : function(id) {
133359             var existing = document.getElementById(id);
133360             if (existing) {
133361                 existing.parentNode.removeChild(existing);
133362             }
133363         },
133364
133365         /**
133366          * Dynamically swaps an existing stylesheet reference for a new one
133367          * @param {String} id The id of an existing link tag to remove
133368          * @param {String} url The href of the new stylesheet to include
133369          */
133370         swapStyleSheet : function(id, url) {
133371             var doc = document;
133372             this.removeStyleSheet(id);
133373             var ss = doc.createElement("link");
133374             ss.setAttribute("rel", "stylesheet");
133375             ss.setAttribute("type", "text/css");
133376             ss.setAttribute("id", id);
133377             ss.setAttribute("href", url);
133378             doc.getElementsByTagName("head")[0].appendChild(ss);
133379         },
133380
133381         /**
133382          * Refresh the rule cache if you have dynamically added stylesheets
133383          * @return {Object} An object (hash) of rules indexed by selector
133384          */
133385         refreshCache : function() {
133386             return this.getRules(true);
133387         },
133388
133389         // private
133390         cacheStyleSheet : function(ss) {
133391             if(!rules){
133392                 rules = {};
133393             }
133394             try {// try catch for cross domain access issue
133395                 var ssRules = ss.cssRules || ss.rules,
133396                     selectorText,
133397                     i = ssRules.length - 1,
133398                     j,
133399                     selectors;
133400
133401                 for (; i >= 0; --i) {
133402                     selectorText = ssRules[i].selectorText;
133403                     if (selectorText) {
133404
133405                         // Split in case there are multiple, comma-delimited selectors
133406                         selectorText = selectorText.split(',');
133407                         selectors = selectorText.length;
133408                         for (j = 0; j < selectors; j++) {
133409                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
133410                         }
133411                     }
133412                 }
133413             } catch(e) {}
133414         },
133415
133416         /**
133417         * Gets all css rules for the document
133418         * @param {Boolean} refreshCache true to refresh the internal cache
133419         * @return {Object} An object (hash) of rules indexed by selector
133420         */
133421         getRules : function(refreshCache) {
133422             if (rules === null || refreshCache) {
133423                 rules = {};
133424                 var ds = doc.styleSheets,
133425                     i = 0,
133426                     len = ds.length;
133427
133428                 for (; i < len; i++) {
133429                     try {
133430                         if (!ds[i].disabled) {
133431                             this.cacheStyleSheet(ds[i]);
133432                         }
133433                     } catch(e) {}
133434                 }
133435             }
133436             return rules;
133437         },
133438
133439         /**
133440          * Gets an an individual CSS rule by selector(s)
133441          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
133442          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
133443          * @return {CSSStyleRule} The CSS rule or null if one is not found
133444          */
133445         getRule: function(selector, refreshCache) {
133446             var rs = this.getRules(refreshCache);
133447             if (!Ext.isArray(selector)) {
133448                 return rs[selector.toLowerCase()];
133449             }
133450             for (var i = 0; i < selector.length; i++) {
133451                 if (rs[selector[i]]) {
133452                     return rs[selector[i].toLowerCase()];
133453                 }
133454             }
133455             return null;
133456         },
133457
133458         /**
133459          * Updates a rule property
133460          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
133461          * @param {String} property The css property
133462          * @param {String} value The new value for the property
133463          * @return {Boolean} true If a rule was found and updated
133464          */
133465         updateRule : function(selector, property, value){
133466             if (!Ext.isArray(selector)) {
133467                 var rule = this.getRule(selector);
133468                 if (rule) {
133469                     rule.style[property.replace(camelRe, camelFn)] = value;
133470                     return true;
133471                 }
133472             } else {
133473                 for (var i = 0; i < selector.length; i++) {
133474                     if (this.updateRule(selector[i], property, value)) {
133475                         return true;
133476                     }
133477                 }
133478             }
133479             return false;
133480         }
133481     };
133482 }());
133483 /**
133484  * @class Ext.util.History
133485  *
133486  * History management component that allows you to register arbitrary tokens that signify application
133487  * history state on navigation actions.  You can then handle the history {@link #change} event in order
133488  * to reset your application UI to the appropriate state when the user navigates forward or backward through
133489  * the browser history stack.
133490  *
133491  * ## Initializing
133492  * The {@link #init} method of the History object must be called before using History. This sets up the internal
133493  * state and must be the first thing called before using History.
133494  *
133495  * ## Setup
133496  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
133497  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
133498  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
133499  * to support all browsers:
133500  *
133501  *     <form id="history-form" class="x-hide-display">
133502  *         <input type="hidden" id="x-history-field" />
133503  *         <iframe id="x-history-frame"></iframe>
133504  *     </form>
133505  *
133506  * @singleton
133507  */
133508 Ext.define('Ext.util.History', {
133509     singleton: true,
133510     alternateClassName: 'Ext.History',
133511     mixins: {
133512         observable: 'Ext.util.Observable'
133513     },
133514
133515     constructor: function() {
133516         var me = this;
133517         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
133518         me.iframe = null;
133519         me.hiddenField = null;
133520         me.ready = false;
133521         me.currentToken = null;
133522     },
133523
133524     getHash: function() {
133525         var href = window.location.href,
133526             i = href.indexOf("#");
133527
133528         return i >= 0 ? href.substr(i + 1) : null;
133529     },
133530
133531     doSave: function() {
133532         this.hiddenField.value = this.currentToken;
133533     },
133534
133535
133536     handleStateChange: function(token) {
133537         this.currentToken = token;
133538         this.fireEvent('change', token);
133539     },
133540
133541     updateIFrame: function(token) {
133542         var html = '<html><body><div id="state">' +
133543                     Ext.util.Format.htmlEncode(token) +
133544                     '</div></body></html>';
133545
133546         try {
133547             var doc = this.iframe.contentWindow.document;
133548             doc.open();
133549             doc.write(html);
133550             doc.close();
133551             return true;
133552         } catch (e) {
133553             return false;
133554         }
133555     },
133556
133557     checkIFrame: function () {
133558         var me = this,
133559             contentWindow = me.iframe.contentWindow;
133560
133561         if (!contentWindow || !contentWindow.document) {
133562             Ext.Function.defer(this.checkIFrame, 10, this);
133563             return;
133564         }
133565
133566         var doc = contentWindow.document,
133567             elem = doc.getElementById("state"),
133568             oldToken = elem ? elem.innerText : null,
133569             oldHash = me.getHash();
133570
133571         Ext.TaskManager.start({
133572             run: function () {
133573                 var doc = contentWindow.document,
133574                     elem = doc.getElementById("state"),
133575                     newToken = elem ? elem.innerText : null,
133576                     newHash = me.getHash();
133577
133578                 if (newToken !== oldToken) {
133579                     oldToken = newToken;
133580                     me.handleStateChange(newToken);
133581                     window.top.location.hash = newToken;
133582                     oldHash = newToken;
133583                     me.doSave();
133584                 } else if (newHash !== oldHash) {
133585                     oldHash = newHash;
133586                     me.updateIFrame(newHash);
133587                 }
133588             },
133589             interval: 50,
133590             scope: me
133591         });
133592         me.ready = true;
133593         me.fireEvent('ready', me);
133594     },
133595
133596     startUp: function () {
133597         var me = this;
133598
133599         me.currentToken = me.hiddenField.value || this.getHash();
133600
133601         if (me.oldIEMode) {
133602             me.checkIFrame();
133603         } else {
133604             var hash = me.getHash();
133605             Ext.TaskManager.start({
133606                 run: function () {
133607                     var newHash = me.getHash();
133608                     if (newHash !== hash) {
133609                         hash = newHash;
133610                         me.handleStateChange(hash);
133611                         me.doSave();
133612                     }
133613                 },
133614                 interval: 50,
133615                 scope: me
133616             });
133617             me.ready = true;
133618             me.fireEvent('ready', me);
133619         }
133620
133621     },
133622
133623     /**
133624      * The id of the hidden field required for storing the current history token.
133625      * @type String
133626      * @property
133627      */
133628     fieldId: Ext.baseCSSPrefix + 'history-field',
133629     /**
133630      * The id of the iframe required by IE to manage the history stack.
133631      * @type String
133632      * @property
133633      */
133634     iframeId: Ext.baseCSSPrefix + 'history-frame',
133635
133636     /**
133637      * Initialize the global History instance.
133638      * @param {Boolean} onReady (optional) A callback function that will be called once the history
133639      * component is fully initialized.
133640      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
133641      */
133642     init: function (onReady, scope) {
133643         var me = this;
133644
133645         if (me.ready) {
133646             Ext.callback(onReady, scope, [me]);
133647             return;
133648         }
133649
133650         if (!Ext.isReady) {
133651             Ext.onReady(function() {
133652                 me.init(onReady, scope);
133653             });
133654             return;
133655         }
133656
133657         me.hiddenField = Ext.getDom(me.fieldId);
133658
133659         if (me.oldIEMode) {
133660             me.iframe = Ext.getDom(me.iframeId);
133661         }
133662
133663         me.addEvents(
133664             /**
133665              * @event ready
133666              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
133667              * @param {Ext.util.History} The Ext.util.History singleton.
133668              */
133669             'ready',
133670             /**
133671              * @event change
133672              * Fires when navigation back or forwards within the local page's history occurs.
133673              * @param {String} token An identifier associated with the page state at that point in its history.
133674              */
133675             'change'
133676         );
133677
133678         if (onReady) {
133679             me.on('ready', onReady, scope, {single: true});
133680         }
133681         me.startUp();
133682     },
133683
133684     /**
133685      * Add a new token to the history stack. This can be any arbitrary value, although it would
133686      * commonly be the concatenation of a component id and another id marking the specific history
133687      * state of that component. Example usage:
133688      *
133689      *     // Handle tab changes on a TabPanel
133690      *     tabPanel.on('tabchange', function(tabPanel, tab){
133691      *          Ext.History.add(tabPanel.id + ':' + tab.id);
133692      *     });
133693      *
133694      * @param {String} token The value that defines a particular application-specific history state
133695      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
133696      * it will not save a new history step. Set to false if the same state can be saved more than once
133697      * at the same history stack location.
133698      */
133699     add: function (token, preventDup) {
133700         var me = this;
133701
133702         if (preventDup !== false) {
133703             if (me.getToken() === token) {
133704                 return true;
133705             }
133706         }
133707
133708         if (me.oldIEMode) {
133709             return me.updateIFrame(token);
133710         } else {
133711             window.top.location.hash = token;
133712             return true;
133713         }
133714     },
133715
133716     /**
133717      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
133718      */
133719     back: function() {
133720         window.history.go(-1);
133721     },
133722
133723     /**
133724      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
133725      */
133726     forward: function(){
133727         window.history.go(1);
133728     },
133729
133730     /**
133731      * Retrieves the currently-active history token.
133732      * @return {String} The token
133733      */
133734     getToken: function() {
133735         return this.ready ? this.currentToken : this.getHash();
133736     }
133737 });
133738 /**
133739  * @class Ext.view.TableChunker
133740  * 
133741  * Produces optimized XTemplates for chunks of tables to be
133742  * used in grids, trees and other table based widgets.
133743  *
133744  * @singleton
133745  */
133746 Ext.define('Ext.view.TableChunker', {
133747     singleton: true,
133748     requires: ['Ext.XTemplate'],
133749     metaTableTpl: [
133750         '{[this.openTableWrap()]}',
133751         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
133752             '<tbody>',
133753             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
133754             '<tpl for="columns">',
133755                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
133756             '</tpl>',
133757             '</tr>',
133758             '{[this.openRows()]}',
133759                 '{row}',
133760                 '<tpl for="features">',
133761                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
133762                 '</tpl>',
133763             '{[this.closeRows()]}',
133764             '</tbody>',
133765         '</table>',
133766         '{[this.closeTableWrap()]}'
133767     ],
133768
133769     constructor: function() {
133770         Ext.XTemplate.prototype.recurse = function(values, reference) {
133771             return this.apply(reference ? values[reference] : values);
133772         };
133773     },
133774
133775     embedFeature: function(values, parent, x, xcount) {
133776         var tpl = '';
133777         if (!values.disabled) {
133778             tpl = values.getFeatureTpl(values, parent, x, xcount);
133779         }
133780         return tpl;
133781     },
133782
133783     embedFullWidth: function() {
133784         return 'style="width: {fullWidth}px;"';
133785     },
133786
133787     openRows: function() {
133788         return '<tpl for="rows">';
133789     },
133790
133791     closeRows: function() {
133792         return '</tpl>';
133793     },
133794
133795     metaRowTpl: [
133796         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
133797             '<tpl for="columns">',
133798                 '<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>',
133799             '</tpl>',
133800         '</tr>'
133801     ],
133802     
133803     firstOrLastCls: function(xindex, xcount) {
133804         var cssCls = '';
133805         if (xindex === 1) {
133806             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
133807         } else if (xindex === xcount) {
133808             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
133809         }
133810         return cssCls;
133811     },
133812     
133813     embedRowCls: function() {
133814         return '{rowCls}';
133815     },
133816     
133817     embedRowAttr: function() {
133818         return '{rowAttr}';
133819     },
133820     
133821     openTableWrap: function() {
133822         return '';
133823     },
133824     
133825     closeTableWrap: function() {
133826         return '';
133827     },
133828
133829     getTableTpl: function(cfg, textOnly) {
133830         var tpl,
133831             tableTplMemberFns = {
133832                 openRows: this.openRows,
133833                 closeRows: this.closeRows,
133834                 embedFeature: this.embedFeature,
133835                 embedFullWidth: this.embedFullWidth,
133836                 openTableWrap: this.openTableWrap,
133837                 closeTableWrap: this.closeTableWrap
133838             },
133839             tplMemberFns = {},
133840             features = cfg.features || [],
133841             ln = features.length,
133842             i  = 0,
133843             memberFns = {
133844                 embedRowCls: this.embedRowCls,
133845                 embedRowAttr: this.embedRowAttr,
133846                 firstOrLastCls: this.firstOrLastCls
133847             },
133848             // copy the default
133849             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
133850             metaTableTpl;
133851             
133852         for (; i < ln; i++) {
133853             if (!features[i].disabled) {
133854                 features[i].mutateMetaRowTpl(metaRowTpl);
133855                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
133856                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
133857                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
133858             }
133859         }
133860         
133861         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
133862         cfg.row = metaRowTpl.applyTemplate(cfg);
133863         
133864         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
133865         
133866         tpl = metaTableTpl.applyTemplate(cfg);
133867         
133868         // TODO: Investigate eliminating.
133869         if (!textOnly) {
133870             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
133871         }
133872         return tpl;
133873         
133874     }
133875 });
133876
133877
133878
133879